From c3a402ec46d39e15277739b1b0bc465a12768a3f Mon Sep 17 00:00:00 2001 From: Thomas Megel <57842400+OpenScanEu@users.noreply.github.com> Date: Mon, 4 Sep 2023 17:03:17 +0200 Subject: [PATCH 01/38] rotor endstop, diskspace warning added optional rotor endstop fixed wrong stacking order and blurry images added error warning if an image set is about to surpass the available diskspace --- README.md | 14 +- docs/Telegram.md | 52 + docs/changelog.md | 46 + docs/img/telegram_bot_watch.jpg | Bin 0 -> 79487 bytes docs/img/telegram_configuration.png | Bin 0 -> 48062 bytes update/beta/Arducam.py | 202 - update/beta/OpenScan.py | 119 +- update/beta/config.txt | 12 +- update/beta/fla.py | 416 +- update/beta/{flows.json => flows.json.tmpl} | 9562 +++++++---------- update/beta/settings.js | 63 +- update/betaArdu/Arducam.py | 202 - update/main/Arducam.py | 202 - update/main/config.txt | 85 - update/main/fla.py | 149 - update/{betaArdu => meanwhile}/OpenScan.py | 9 +- update/{betaArdu => meanwhile}/config.txt | 0 update/{betaArdu => meanwhile}/fla.py | 63 +- .../flows.json => meanwhile/flows.json.tmpl} | 9505 +++++++--------- update/{betaArdu => meanwhile}/settings.js | 1 + update/meanwhile/start.sh | 11 + update/mini/Arducam.py | 202 - update/mini/OpenScan.py | 185 - update/mini/fla.py | 100 - update/mini/flows.json | 5500 ---------- update/mini/settings.js | 489 - update/{main => stable}/OpenScan.py | 125 +- update/{mini => stable}/config.txt | 11 +- update/stable/fla.py | 351 + .../flows.json => stable/flows.json.tmpl} | 1226 ++- update/{main => stable}/settings.js | 63 +- update/update.json | 126 +- 32 files changed, 10209 insertions(+), 18882 deletions(-) create mode 100644 docs/Telegram.md create mode 100644 docs/changelog.md create mode 100644 docs/img/telegram_bot_watch.jpg create mode 100644 docs/img/telegram_configuration.png delete mode 100644 update/beta/Arducam.py mode change 100644 => 100755 update/beta/config.txt rename update/beta/{flows.json => flows.json.tmpl} (59%) delete mode 100644 update/betaArdu/Arducam.py delete mode 100644 update/main/Arducam.py delete mode 100644 update/main/config.txt delete mode 100644 update/main/fla.py rename update/{betaArdu => meanwhile}/OpenScan.py (97%) rename update/{betaArdu => meanwhile}/config.txt (100%) mode change 100644 => 100755 rename update/{betaArdu => meanwhile}/fla.py (89%) rename update/{main/flows.json => meanwhile/flows.json.tmpl} (58%) rename update/{betaArdu => meanwhile}/settings.js (99%) create mode 100755 update/meanwhile/start.sh delete mode 100644 update/mini/Arducam.py delete mode 100644 update/mini/OpenScan.py delete mode 100644 update/mini/fla.py delete mode 100644 update/mini/flows.json delete mode 100644 update/mini/settings.js rename update/{main => stable}/OpenScan.py (59%) rename update/{mini => stable}/config.txt (95%) mode change 100644 => 100755 create mode 100644 update/stable/fla.py rename update/{betaArdu/flows.json => stable/flows.json.tmpl} (89%) rename update/{main => stable}/settings.js (92%) diff --git a/README.md b/README.md index e9478fc..b08bccd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,18 @@ # OpenScan2 - 3D Scanner -## Related and more specific repositories + +Please take a look at the current [CHANGELOG](./docs/changelog.md) + +This is not the official OpenScan repository. This is a fork of [Thomas Megel](https://github.com/OpenScanEu) work but completly user-centric. Openscan is great in both software and hardware an +and Thomas has done a great job. But openscan has those things that: +* Users hate and complain continually and there is no one fixing nor acepting fixes/features +* The workflow has ups and downs and some interactions feel buggy. +The aim of this project is to tackle both weaknesses. COnvert this to a pleasant experience fro the burning of the image to the exporting of the model from the scanner. + +## Official Openscan repositories: If you want to take part in the development of a specific part of the OpenScan system, feel free to join: +* [OpenScan2 - Official Openscan files](https://github.com/OpenScanEu/OpenScan2) * [OpenScanCloud - Web API for photogrammetry processing of image files](https://github.com/OpenScanEu/OpenScanCloud) * [OpenScan-Design - 3D printable files and other design approaches](https://github.com/OpenScanEu/OpenScan-Design) * [OpenScan-PCB - A place to discuss and improve the PCB designs](https://github.com/OpenScanEu/OpenScan-PCB) @@ -10,6 +20,6 @@ If you want to take part in the development of a specific part of the OpenScan s ## Contribution and contributors -The project is based on the contribution of many great and open-minded people by doing tutorials on Youtube, comments on Reddit, publications on GitHub and many other places. Without all those voluntary contributors, this project would not be possible at all. Please feel free to join the discussions and development preferably in this repository or on [r/OpenScan](https://www.reddit.com/r/OpenScan/), [Facebook - LowBudget3DScan](https://www.facebook.com/groups/142108429832711) or [OpenScan.eu/forum](https://openscan.eu/forum) +The official project is based on the contribution of many great and open-minded people by doing tutorials on Youtube, comments on Reddit, publications on GitHub and many other places. Without all those voluntary contributors, this project would not be possible at all. Please feel free to join the discussions and development preferably in this repository or on [r/OpenScan](https://www.reddit.com/r/OpenScan/), [Facebook - LowBudget3DScan](https://www.facebook.com/groups/142108429832711) or [OpenScan.eu/forum](https://openscan.eu/forum) Thank you! diff --git a/docs/Telegram.md b/docs/Telegram.md new file mode 100644 index 0000000..ec8b080 --- /dev/null +++ b/docs/Telegram.md @@ -0,0 +1,52 @@ +# Telegram Messaging +### Why +Would it be great to be alerted when a long session ends? The aim of this feature is to have a way (in this case, via telegram) to know when a session is started (giving some basic information about what will happen) and another message when the session ends. + +### How +Its only some conditions along the existing flows. If you have activated (and properly configurated) the `Telegram Api Token` and `Telegram Client_id` you will receive the status messages to telegram. + + + +## Setup +Everything is managed by [botfather](https://telegram.me/BotFather). Follow the next steps: + +Request a new bot: send the command `/newbot`. The response to that command will be: +``` +Alright, a new bot. How are we going to call it? Please choose a name for your bot. +``` +Now you name your bot whtever you like. This is how you will identify your bot in your telegram user list from now on. +After choosing the name you willget that response: + +``` +Done! Congratulations on your new bot. You will find it at t.me/openscan_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this. + +Use this token to access the HTTP API: +4815162342:N334AA-covfefeskibidi +Keep your token secure and store it safely, it can be used by anyone to control your bot. + +For a description of the Bot API, see this page: https://core.telegram.org/bots/api +``` + +This is a really important part: store that token safely as its the way you will securely talk with your bot. + +Now you have 2 ways to fint your "client_id": + +The first is open a chat with [@userinfo](https://t.me/userinfobot). If you type ```/start```there you will get an ```Id```response and you can complete the process. + +Another way is opening an url in your browser to get your id: + +``` +https://api.telegram.org/bot/getUpdates +``` + +you will revive a response like this: + +``` +{"ok":true,"result":[{"update_id":462349, +"message":{"message_id":123,"from":{"id":1111111,"is_bot":false,"first_name":"Meanwhile","username":"meanwhile","language_code":"ca"}, +``` + +The Client_id is the "id" field (not the "update_id" or "message_id"). +Upon completing the settings with that information you are ready to go! + + \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..af4cd9b --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,46 @@ +# Changelog + +### 2024-02-26 +* Fixed: Without telegram configured it was impossible to finish a scan (hanged on finish). But who in their sanity would not use telegram if available right? +* After a reboot/shutdown, if you forgot to close that tab it will not trigger a disaster when you are scanning in the future + +### 2024-02-23 Meanwhile (23F) + +This is the beginning of a fork. After several attempts to add a feature and get silence as response I have decided to create my own OpenScan fork. +* added: When doing a focus stack session you can store the contents inside the zip with all the photos sorted in their own folder. Some focus stacking applications will benefit from this. +* added: [You can now get alerts on Telegram](Telegram.md) when the session starts and the session ends +* fixed: The "delete all files" operation no longer prevents the refresh of the file list + +### 2022-05-19 documentation +* changed: overall structure --> the OpenScan2 repository will serve as a central hub for all informations concerning OpenScan (i.e. firmware, hardware, tutorials ...) +### 2022-05-11 beta +* added: changelog and version (finally ;) +* added: create an update using the node-red-backend inject node ("create beta" and "prepare image creation" in "update" tab) +* fixed: Error handling in flask (when no preview is taken) +* fixed: Error when upload failed + node red restarted (multiple instances of curl) +* fixed: When closing the browser session/missing the popup after the routine, the data set got lost (if this happens, just restart the device and it will be moved to the right location) +### 2022-04-26 beta +* added: donation button ;) +* fixed: the wonderful camera position algorithm was faulty and a bit inefficient +* fixed: downscaling the preview image caused the preview to disappear (when crop value was to high) +* fixed: delay_before and delay_after are now properly applied, so that you can set a delay before/after taking a photo +* fixed: updates might crash the selected camera --> it is now necessary to re-select the camera after certain updates +### 2022-04-21 beta +* added: timer (ETA) until a routine is done +* added: showing progress, while files are being split (before uploading to OpenScanCloud) +* added: infotexts (FINALLY :) +* added: several stats/device information +* fixed: combining two sets did not delete the smaller set +### 2022-04-20 beta & main +* !fixed: pi cameras (v1.3, v2.1 and HQ) finally work and can be simply selected in the settings menu +* !fixed: Raspberry Pi 3B+ and 4 work! (the main limiting factor now is the RAM, where at least 1GB RAM is needed) +* fixed: live preview sometimes did not work. This has been a network speed issue and has been solved by downscaling the image (resolution can be set) +* fixed: it is now possible to delete individual sets. +* fixed: it is now possible to use all LEDs. +* added: Turntable mode (disable the second axis) +* added: Pause scan. You can pause and un-pause the scan by simply pressing the button +* added: second scan pass. When one scan is done, you can immediately run a second pass. This is especially useful, if you want to re-orient the object +* added: auto-timeout. Turn off the ringlight (todo: and motors) after 300 seconds (value can be set) +* added: diskspace warning. When free diskspace drops below a given threshold (4GB by default), a warning message will appear +* changed: new background image, minor design changes +* changed: log file can be easily generated and downloaded by clicking a button (update&info tab) \ No newline at end of file diff --git a/docs/img/telegram_bot_watch.jpg b/docs/img/telegram_bot_watch.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e0728d8cc5b977b1fd5e2c0be10b3e68c39c56d9 GIT binary patch literal 79487 zcmbTdcUTi&(>K0=6hT0gUX>z9r1vhOARxU2sR9DhdoK}?-a$Z-ru5!>uc4##-dm^% zHMH=H-}`><`}yPdUe|lQlkDy%XXl)qnVj9s%$eNH+^qrwZ#*o`0YFI!U*23mehN@E8Ho4+F2+POHX%e{J{^IrD}?$*Dw``gRd)Y(B=RrTFp z{6ELP_`h^M_16*rFwXIJto_@pXyG8aF5BuB6|Xr;uK)7=|04X4X3nPQGdJjs+tk6y z1#Qz8Xsqh);_w&ypfRZvS|~I|n+71Y_!rLpi%tH8>;K`=RF^^X459H8(@(}9(Rd$? zUzq%_c8mXoZEaltUiUBkmHvU5ot6grNQ&NQf!BZ~;0!nbHh?kU2|NK*0lxnOKmR}c zGJrjr+Y@j?_p<;#0WRo1(&(1IBbcGFJYWZy0-V4LH0A}k(HrkyJEQxe>3s6v_IvT) zw!mcokZ8NRyKDMyn?f4^R0sh8e#3v;*n|Oqga82g8XSzBjQ<(#@7%;RN6)U~JOChk z4*-vUqxz?#KgLXg{FJh_x=(#4)#BU^KZiahw%Q={r}KEy)iNGq2Kp$uyOv={(lYb z7SXer`K}!x!NWAi6ve_|1TaZ3ut+fOx-ovCzcuWC1jP79DfA94Ct5IEy!-eB4*^UJ zEG*1>SlDQJ(Pw=B-az+Aupd3;k;ZwfW{k_|NXqLU_Y;rlRYeDx`X4wmpNUhzeSC5X zN-AoWXRK_`+4%(og@i>!WnRCLm3u3%prNUy4Mv;Y^rM-%#V1QEXBSsDcMngmz%N0; zA)#U6@d;lOlajxEPx+OVos*lFUr<&aE%=wGxGZ5k-m!iSuP79sYs&N1<2#uA*y(!UyDV2hcm2ZXM>!z2?(3D91vfZpSHtZPP{f(51~(tTuG>Q zFLGBewoVi`DK~40=A|ufFZoGfVyDx{IDx&R7e4$gdQ$dUx1vUdIUa}E0rWMaNATrG z#Fch<;)vA&ovBjCyD80z23)KTNNVX15Y7mjP7UjJe9#ihvkzB;q5-=_Z8dxcFCnR5f;A3> zYq>@}ZjtA4XV4la!UB)@qh|s(#8#5UX5umudtFOUW0YH#DS-g7@QXWu*vCCVDQ16n zSK;&ljue9xQgURl`zkyU9~{k1EYM~PKk2SxqkHGya;2fe-*+{U1l^&y5$G-Z&}LrO zQ0w35!paa^imP*5Ch(@73sUr!*cfvK!hRV*?0xkiE$kd6XPXyx^6Q4oRYHCi!u}xD=k&Ib(xWRsxL6zTcZ0G1+}%ja+@T*7D-Afo_GM?nEmTGZGP3T%uh?U&T>A8gjMWn`8 z@a!wiQV*8ouA@-*jJEps`cibtIKD*<2=iwk81Wjm#aP=hvEhhRKOdtB(4#j-Fs&X|WLZ6wSxt_cSdqkFxFU2j zq%v?<8Ygqr&+SNvclI}UI?=2x%du{8%1ZXblQ`^X+PHRZ<3rUpS?bo(X2*_tGt%GW zOV7QL_aaX$YE1hPh!lhlxaL!cB1*`B@<6Y7ZU{lN0wgtrMP5D=t}voc;ql@weOk_a}UMy9{Mv>}y}p3d>bHv5J2T6U?OS|#(9%dwV`47*SC zM-D2yUL!;~Xqp@F`|@gB9U10JB8c#`UN%9zC;a83SfAP`^6qp*?xZX~XZpo2gWqx$ zkeT*dB9O$5b_K&?ko`ru3QTo%z|I4^%wO`^lCgO+J56+FjZ4`Vlt^7``0g5VKZdz< z&H2Evdx__3>KA}$rbqq`xW5qaEQ$)uL|NedD7pFj@WQ96nOEYI3rjVI{ARR~+BMUW zbmN0hl|4>N-E7@N^az8lz@&*WnVnlP6_{dP zn&~&JRG{u@Ga1VHxuQc&DFHK<^`IW@q&0nu^4x;l_s#aUai14Q?Lbb1y4?Ok&ZlLp z4GnS8+O?-mD+OWfbfo730l!b(CL}e6!Fo5hbK6SoA1}SfADL2a{rpqglP2F9TZ+G; z89Rcvr(e0y@I)hJ?b8g#SYD$46n`IS>Ulo7xp88tk0D9FxT=6mGrNnpdPT9I=wd&! z{uvqd^LrvYAjvsLR($KXyrF%PiHS0m!M(+gm>v}5C8_f1ki6Sz=Tc}k7xOGWjDo(~ z{bOs`P^g@pdY%{d5Kj1DH;dl@uY`FNZ#_p7HL2EX&7?a?Me)%CrZ3&nJgTy(JAN#T zkRZPMeRKGj_T=67#1Jx~&0I0eFI@VLMiCk152V;eHpxR&y}3ub=b3!3$sfLQ95i06@| zP7AMn=Fg2y9JPWbCJPtP!vwz!tAC$9BARq`d!}k{yqzPhTHXEBZlDA zQ;`Y!HYI>vTf;0UvR3<`*(cj#7u3`I-fT@mQFS)=)$wa$Em#Wdr`$JXH`ypuo;z%f6HqXR@}hTVvJP=+IP;_Rq?HRC;_X%)zR+v%zT$bv#7`ZH^W{Ku(tR>P4;)D zHy`f5=YuLk61A=MWUkvu3y&4!Qnf~rni(YY8vF%uvmWbbdHrx!C^*pN&_s9yN2<3b z2=AMuWo#DAF}C;*)_jf7fv(q~?T#2&&FostBORt?=ors);$&?{JX>ZM{U&8?g|OLn zFHTui(PUkzG$e&)p19HC_Klov(firAT4oCjfqm-&hq0)UWTFtC4aeI*Dlz9aq4g(+ z8C0}31*8Ro6{};rSF}f4qZQv}MVS2GbCmAD-@4UO+{WX=jO5>3^T6i&PqN$ZfREZG zW&7i+9Wf$lKc=z5@ve$DD^Bi7@{gNbJ~d~6KjG0M@i-Rv5|r|a=XW_~>$i||S9ms& z&!(jmfNd=eXXtRuyZSOBO8=F3SAaNfdJVgz9oh4@2yN>HU!Ja+&_<{0Jk3|vB|v#J;6L8q5*EIZby zX1_$H;hK?Mgxoywdlb$QVNGSX%wc{9bUt4_n18e+?jpthn>8S9G~VV+D2`k{%P~EW ztj4;q$L{htt;GXoq}mq3|5}Bu*Wd5!ycE%=xOW&~M81yihxT>!I8@R&T<4+mnu0@~ zl(gEr9w7!D9;fHpSQw5*vQa{^f!_|+DGzZKta?l~gN32FAf8*D%WjW|9^nO>aFT=Z zx3yXY>dRkgYpaUmMp_ir#=8DrQ3S8XSLs;|j^`$@ZD8a@-T`ar$jJ#PA*%l0MM z6}LME|30O7mVe){&K(fcv8u?@t%0rUhOOqp{SztifoW)%DSctBw60T$S+=4u-Gg;3 z-{8g^zLM<1ci(x(F=kU|+{f(?$xVI-L@mWCv40EPut*#-XZPLwwW-rORbLr<%XlgC zB!$Yjvb&4gC>cXotJDs{!^zWT)^w(%i8UA7lc9qlT)UM8!MmiZ|J*fW&=`5j5TxEk zcUt9+{n0xV4i1gEsj%@Qdv(&45|xrsEjtL!LxTS(7LPhcza=w~CcE-vA+QJ>%he={ zF7#}^Oy)tPUC9%;w8JzRTX=-2R$Q;Zc{gu#T|%tlt48hGdQn<_jU40{zt5 zGgr+5X2Of~IkphezE%wjGP6UG#Tjra?BZJgb(8__oPLZ|Zm|oU=wfuCm!`TQ4^02Y z-7>0SPXkw^$AX{lok2p63Qbcv!Fwr%r00|l9>uq5?R(YJ4_oOf^nM*Uca%xhw(>L> z)_5$(icSm?Su2-4pO2_i=6>FL0B&)R(XffOG*V5BCePaKEo>5l)Rj8CB#VO-F!nJm zb~)-`sYzo|=~0qxyNfo_3}2&O^ZV+I>n zxigohU+)da|<5tdF(4e-PvFxSX*y89VrkWl`2}nz?g=2aODOx#Gu> z@tA@7WvnRCtu^nDT2Z73sG(yF%NQ3^fG-~~aKUC@aUisG;#|H-we&`PmXj)ne> zF5+vsKkZ|BZ%mIHHnn~FC8>|Me3GEb+S|#>vgxr{kKEH8T z9sSIvCCW=wXg^qdxr&#hZ{yDZ^kB64DKacKkBf2T4ya}?ZhLgvdClYsRR*m+U1^IP z%OoTMQ++U#kb1~2fV4*_4Q?>&95hC2(=S|$s)Xvge{+DItbOx)s`DBvSvtl6qJZ3; zUsdUON2?GP*WRC(5y0@MZA!^2T<D=SiA_C{dyoigiv> zzFsXx#z;J4cDz@Jfj^mT>C%q9(QgRFz2vr(c`79-l}H;??(MuIgK$<31Z~B?9NpB^ zTbs=MqDTYCUxB0bPyxeEddvdD&YD$Z-x3D#I z&jQ&((>oN8DN&ew!(r_n_wfYtgZo+p*DP_5vtG!AKxfk^y{^fR0#q+Oq+b=HGMGnb^PaE+vx&v|HzdDbFw1D7{S*K(gs%eqg z8&&Bn-$af+`e}J6^TAEH?B_mYB^^SjzR{Pml?g@q&ylq>uA#>XkR-ayXqzaL#?8C~ zNKzzfBs&xFDza=wN9&$>nYuHq8d*WFF?`4^7A}OKzc0H@ zPjn|#W(yyi)|ssZLJgO*tUb2a z(d!P8(0bPJ5r)SHYJ=j1rgWFE)3Pz#+dvdyVJyr&(&fbY4yc@>Ti(A94={hvv9$Xn zPna+en!{Bx9^D7g$gh>eGzw3VyEnq^TcKl(0Lxm(RLgefi+{NCwLZn%M%~|o*Sc|F zFJ%fjqUsK>SNzB7IrLEX6IUf(X6ssn9+hON&aM(43#j&v?S>{jDu7Ri`3Zh{{!7K0 zifBVnt0L93#;vVlI>P2gdcEOSXlSDO$xEn><7wH4>9&=xbCKU6L>^UsxQpqWt+Qlf zKE$_QTA%nlM#N3BGzRDp5#cC1o}#oSsZUdVr_5f^nKAo?Gb^2I-oGfnf2CdfgL!J@ zVEy-%*3WybuR3^x*$a6;`$MP6Ak4ck6aDf9+xGvIuYsbG>bYe>>mFOe3PA94)rp8+Q`Ew$9m^#%u!B_?1SowTCAKZf{GYpc#|m$l{C_OkF4K#k>?M64O^#%}V2{$SO_=$5f8HqyOrQq{x*n?5XNjB!RKU3P#u1rg)w+;pW+oH9uBG z)TFiZ>es)9Qi`K68=96wb>*cWIG4&PUE-wtbf%hCEI3s|@GIW|PhcvN&$MXE$&RyI zB|_Xdwl-YRkLHl$!b;>J(}`GsoO3%vknIB=_BPYq@wJ&B7v@@*{*p8Fx7dzz$q``p zE3xa3PPm&!Jj9^^!gs(J@dy^DY9!!|9)x|xRV6gpjC#qyUuV@wSp2kI7nb*>Axhuq z`B`h;x_!AQuA~Xv31b(8bJ5dZNnt5?*td6qj+3Lxnms)kJs%D-* zGC7~qE$R7g#`%x7>B54sX~&^%lHJV+7wzq5FZuq2jX*H;lZw{6@aD`^X=1)IjJ291 z8#JfhZNdv?|^Xj;l9-@+|x2eHClB$Cv4BWAOSodkyS+yC}gScGq)O;OF&8e zG%oKa!S6#V@#!jsj29#-N<@d&KU9e>D51h-eRsed?h3d&CX|6ps``f0pF>UWJ1~3p ztW6CqCLS_IjlGh%WuC5$C=upm-`U3mx-Ikx)$twk7%ez2Y`kwgKLyXY1IQ^q>PbA}sVAd1y^1;aWC(Kce(pvw z4daNJwnu77st{9At|SQ;k@)ZcYutEO(`)7hXi3D7HO^M$mUFhL4~Wg<+D@P9xJ$t` zjABY~6G_QsumXdna##ZjID&4=6vVPF-0Xpw`N6Q21;JxEY3L?cYW3cDw)*xU+q~Ox z7Udm)kMiC|s~zFv)ktsg-PR6Q>=Aw5v`8WE9nc{I`SeQ4Ilue$Uq^D&qUDug41RHg^=l+*^D97vbqrBJIhI1~(9?nul5uUw?H$9ct=U#5 zcko1+X)=ponS4izD<%2d^_5i3`V1<`7Dc@?vO1dd!~4qghCLDL??Ph{*^H=ocR_YD znRifkEhD(jS*uAhN6OG&UrZ-=UWPpL*?W<4)0Z9nE$dn|6JiamxD|P`nuhrje?gYm zcrRQVei)i(6YqZzXEYc!eZYcfJ*51NjNX;_d4dKNeI(!gnCL#ORq{>XZ^*&(N87S_pDo)iGOSUX>6M zX2^j&J3#2IJ%7V6FZB81J_x_W9UYoc8?v3$=uO=LUaLE(zSy=}v}LN>>Y5)NUz(qb zmwkZr69{Pn#wAs<>dn$KEd zc56@0({D3uB=yqF(U(1FoO3=(sjPjrKKh)a(K1 z$A^a~_;?2FDTIa3Q=RJ!EfriVh%I#Z?1z_icAyJ=rsyVjJ~XRBf%wnq0nxC1l(t6$iC7L=Xn})*`O(!!}9y zgI)O{8d2AJexn5A-Q-ph8(5cXwfv)VDl5@H*}}&Ss*;CRF!(*T5o>LUY^QfXEdNe) z*Q6|jXi_34eC|<2zU{(c?^v5s&8P-jShaJ*0;;J*lEp{3{jB>|0cV=>e#zw((B(bFy_u|8+A+d5mk_&_rWlw(ZvsLLN-fbSox4X- zJHV3`9|!$?I65xyNi93bu)O}h`4PfPwHW_gbRm{l1W{X$7MOxQSw*4wwBtn zxRwXIgtHk{bK6}-O_Yl!QQ@|`A{|W8iHM9IVvId)XDVIX7rt%5O^`3>*icO+cnO_8 zlbzJtC+cyi+`a+rmq;1Gxnhb1d-%a1(M<*3gg)4TejB(z(Zw5S)5&mrkP$897C<*Y z5zzc063jL&3ks|YHgq&9q!+o(s2)}@^~~-iCtefozXh!>)7?-{xqN4d?|tSYMA^QU z#@?SHBg2rJmtvU`sNB{p47Hkh{8_pb`$PR$=}knr=?BLE&`%0_8|k%evXsmjgQP%&Wn>9{LS!G1jOVi z_0y&EH;jj(YU|%CPf7R|jocU&5^!*@mTw$DYh0FGDk3|enPtqA774l&OM-WRW(e-y zX$fPoqX~$4G718CZY?QZ@(wp`4y&vgJsho{_y7GNe6B8OT50?X$>%}}k&99S4Gnh} zLFe=~g{&WY4;pf31lEh=)Bc=c{XQ>kSF{VXln^de=qE}EiaqSRDSYhtTqJXhj1zJ< zjxsuIWg5hgbQ2=)ED)i*5d%Lj^?4nY;G)i6zgy70egz(yZe>g{T6mM>ya-vHmXc+uW=Ecs4BqiU2c)9hB(b2DNOYrFV`>7Y>ZY}vT)EO(0zJGKy%ih?yjt}hh zADC|BCVkjll`BzeYHVm?;cnwnCiOGAwYu(Tvw(z%ioe?0x=c;HoGHhmyP8l%vaYnG z&m_2Bs!P5GsC(8eOG9!i@8=XrNeVu08l)K!As9U_&FD&JXq@5%^ejM;0PYU9@3{4< zFO6r_SoOS~n6?zVF!pGZ&^zs|AFCij!4nD<64t|FCK|txJhGs7HikW2AwS$3PH=<$ z2&}J~VyAj%abF=BzKvK<7DLQ`(7aY$Cb4?EgD0u^@|3Qg?8r?NpC*$jF6PNG^b%b2 z$%xqHtOoUal7KO8vXMGe+u5hwBK!D?bk+0qkK7g^yKw+tOg zaY`GO+MlB42agU6<8pSAC7;ym`95Q)n$J8wrZ{pd%eoO;Nn`4Av-T~nEDYL)&`G9& zDw8M@cRk6hJGd50hy~$CU5ZURbB9;F;XfQo{73TyVD7a`58y$8Dz1!0X$^x99(bQp z-V|-v&+(nZ;ar^_+wh~1w((NQ-Qs&>8mK=u4iXJz-DMFOHC#O^5y}0xC7vub-#Nb7 zMvsW$z+%1ETC6<05;^|Rv*?ed@_S`eXOKcxenRd`(iAr;$KC;TlGK_dU4^J49Uiy9>>iY>eehE`}xSgVvB)DUx00_#8oaW*he!Z~>cmEo3LLbmj z^m$HOopkbB9P_4Bq8>@+feywOyuN}MCd}GN(*;R2ZECjjR4yxEi<}^7r`eB;YxbFa zvc2wd7@6oqPXaAI(5WQy)n@|nfp4rPgmj&ijtxs_A02uYvOCmRI51&m&3(eCR(pd} zKEsxKsOyRy^mIfx$LL3nm}Mpbm#}QKqH)bMGUOhE)dg2DL$y%%XfQa!_17+MVjqss z2H7E~9jcq`v`&_&(^Di*qgKLZ{rF<~+!yV423bn7yme8bVXJMTG}H^GlT5sNU;^Kc zu2wlv64YYpb!d)6J55jGR9=9eyJFz&$_U z3So!yG{`njjQ$)vIW%%BxGF(_R<@G8w}Jr$472uAGDLdOueK1*^!9UsEBVz7dZflW zaqx$!YLRi5{+L962?vT9iZ;6?HA_mK-SS_PzhMv{ z7d|qV(rq<^RB9VdTf_Be5%3+CCi^1bbr<#NVlp-=-V#%@tq^ zJRVpjCJj_o<(5-;p+0STH-VDzVS(FsQ@30OP|6>-KN#-h^fHTK_`HR7k2;sD=-WyP zI4V@>8ZbgOWe=9{=tRLe$>dd`U-uabuL@#Y}D2=3@H(=-xvB2!0xPd0IBW z8kI1c(O`@;kH5(FNRPVa^AUs#1dKWaZz0Mt>Y1z(Cj1W!$Bna*#Ilw5Z69#34o2l@ zKJazackfmlCD_sKCmri|cIn#GwpoeEBU@9k%=d`vwr$0`eH<@W3yKyn;|e{u>*`PmBLjpn&N<3IOlrYXU7rHAEvQG zAB!TJD!J$Zj3Bk&{;Hp1mp!6P#h$x=r1+VX2C>-x_rsGNcJ=?)ec@1#Nv=zWGE*h$FGh27O$Y8N1Jfg z&t{*xU#Ko}YQ2O-KIB7!(J2$u%vD>rg!XnvI!*oYfly-HI1`2kyQnLD;$@E8&@qnr zi4g6^S(!an^1#B0q#|7kR{%!7BFEYI;)0kG^Kh%2iL%~Y!M^mcJacFUNXtBlJ3C>l z|={UVSNN$J(ZDgg_95b-ODCP<FH2kZ@VF6a!Ly|shvGBBKapNKYvx$I}4vaJ4uNVDWqm=z zaUL(6k13>3PnM`e@n(@I(w0fU^SxeBarxNC2U$V*am zZh#;wbKzGOZ?5PRHwD#^2K&2Z*H36>&X0ymVj2&k!b^Ya7aoFoWYuk(Ip_xrf=1~# zTG6!(LSCZ~UP)Th<9wxdy1yM1yL%;+qSkA;`;f&M)g?j)j?s5Oj+7~9mrj1m_Zr&b z7AqsEi7EWWzeN}?RvW34l}i59i1w!eW=!Ei9OiGHg2L5@L?>!YHHV)JY7iu}iwEt9 zX^S>2u=l${^)=-UbnM^m_y%5!`TeHO>%0RV>UDX7Xecfz9{@k#!q4_YQq1EOnQ|`*kAHjTMt+%&{eo}8%C8WhU3i>O_F|?{;`JkJIvaFoi zjgk?dVNdl4$)AW;f3D!Oj4Gba$Ix+jDZ6=`nklVMJBsWKH?sO#iqtZ!02<#T&wo$gGoFGTwXs`shW@r>}KdP~9E`XAW7hh3SVfLkVlFHrF9}=g<8DU75)2aqGO?uqGnO5B86xnLX10Ac$OQ-mbNk7>i@wWIWs~cr{qHgEb znt{xa{h4Xx7)N!C%tGmBt{OwVPj7L_qVD}6$}RQ%*%m^X=~!eNb2oVtVJi!ji^@*q zUPt;ACvS*Uij{nB`c`2mg284g2O>z9)jN{pozn`YYnK0QV*S#q`QSlJ=%UMnC0j z?6qy*;$L}y&dV{@aMTRTe&m4#_SGrlctE$OA}FLkxlC(c$tF`aHzkjJM~Vkt;cu4S z$TQgo_q+rcQj{YOvr6UcK#wjoTum*S<;BjP$^vJ_{lg_$Aj3Wh`B6YSRyMwXtV#5M zv-d;hb0_SPnbMq5cYt{tT46OZlyLX#E*6>2D|6Z$*xVy#s*P#M7$ zA;4BWk2y7k#Hfw5m}^smSQrH<=DwF{QN#o|eJus!o5UB~;rrf4T}QYxFzQb&6HGI4 zrDGnaW&IgQHkAWi;}oNibjokR8An0Pw)cDa#xtyL8E?z|_+b#-R$4{6OnRqWNyGcL z&EVtZwFA409z({)sU!V?i${E@llaVYvI8?6GcMl@+#hR~#(gm&@_CJ>0`2ssNvx8& zzv>47vGB3xXAFttcrIhoZo#o!g6L5UjOg|Ag&r4jvSdNoN?%;(=q^Sy_%De2E3SbeD2utdnDF-I~ zO)*&HtbU$<7SZS{h&Pz`Y0WFaiYQp9=bwpD*eTvWcrC3EV9m(=YO17(OeSUL*Pq8< zual_<<}EMf6bjF$eqes)lM(;H_nvo1b9=1GGD|>2;=8d8V50EAkM!4w`0bx7t&a_f zSDNXjS6U@*>}O(0)N@br{NL&PQQdqay)D4?ZFbJGenz2aHsLMe+xc3R-K?4>+X@0) zE_Pb{BbnZuCKIMvpfLTm)OyF%&{ZIeFCV5mT_ravFgi2{#;arpVSJ4X)ar|jw9Dxe z$B!{3;sN6|fS{RKoa)7$Z)7&pC*?UUq*K#D76S=%?8Si_>|k1Lr=P?5Q_r9Aa_AcO zSalR^V`_wNi~o#Cu6ac_=`RrUPN3N9YlSG?hDLG7#*=lJ_UoV!vW_laadj8f*2y}Y zO#E$e{I#+-c^}_&SXmRkI#OzhKA&N4jJwqKp#W72dhnO-`W;D54)4x~`%MI&)%p^qy3wkJ1_V?M3O=!aBQS#+s5E`o?k zrz}o1uKKjUY_g29uayhlHz9=kfP&`jS~IOJwz9?SCgpVx)8_oJ_SDp5B$lJ96EWGQ zsB89IdcLboVQ(o9)Mo-EGHIleSUvA2&l(2HYN%Q7 zrI&thr)RuXZHm~6$X%C1$qgCgpNy|uYt7dU)_rbGbL{z`_bHui+Egf|I&1Jp2G{sp zDN>~);LzwmZ&JiA`{D{*RWQyN#Z+hZ6o=4%~ZoOlq5*(O}J zpHusJWH4HyXz@md@D2#Ap=l#AI{dWBym9`tDjeQMzIeH!*X>Z$<(WQUm#4f6)fb*8DA*lU9WVju8 zDl4rMGu#WIDJX24$C?MInu#X4$%dvSw#TG=Khh$a`wlKAMA5(N7z%UC5`G(!9Jhn*aR;CsvXw7J zgNR4jtiweeg@zt7W+?Uwu@!s@}tF)awXzO`%YgGTHa>DkNM$DgcBd_5&^2xnk zFDj*}#4MtpfvVjHwrhMsM;*QtagMe8h}qVRTUm#H3n!Y)GW}_$lt)<@EZAdQve_&B zWUctNI@*`5qC=5vrqa)_+TVXg+jRG1J!L`9;af_w)IW9A4Vk3TR-a~ldg#{*JhLAp z^t_F3cp#>&^0HCTy?+E1kahi%2{ZGADtTrcGj15inKQealaMt{s}Ajfmlu(rKdENf zCi{6F%h>Bl7L)3%bfTgFoo#K`_fC#Ipo57w;EELX4c2SHrD1YazMrkgmxNq~UC8$+ zyR_M#vxYC$y3pL;zUQbIw{IpQ@Ux0q>icG@PyHxUi!M0k%P3b`R@oD6ZK5{tdA`%xIFhoeRlax8DQ5R<50NEfL?nxF(BCIR4RefScn} z_RukiwsOFtA<5yisMI#@CJ1J9-|rdn*FzNfAG@OvwM|)Nzl>FVzqVBdat~$uDrA#V z!#IQRyz}GN5W;o~c-g54e7TD&$cU)8H(@o3cj}K8Pu<{46*|6ocv(=J6~nCQsu9%J zd|46Umqzt+z-NQ5>cSU7jZCG8RB&x2Bst|<#rJ95lL~wIO5v%>w{>x6=rI}Fw?Fj7et(St$(XTUvBrV%6u#juYBn45>?89@G|&7M7`=qo6N7we6wQy_IO)e=?P!p# zFGmPn?iI6L7dWdoBCVVo+blRt*GF<@n`{m{U*4!*(vYT}0*|lof1?H_mY9U=oT<+o zvI}qDu7F_-?HMzAJ)+46Z?b$ykv&!~G8;I@xuL@wv|lySvMZxSdLUpF9h_@F>YCn1 zG*ET=M#`>spoqRFSa>p&!Ltrz)|%!GKGG-dBt6N$w$kFb=cmy_s}VAgTc8!;1T#fG zN|~9%j$f?}h9yiDD%rF2>f$Y4JLJI+jFzMhK-cuBL5Au(;IrLX6q9uDl5D8BI=d`A z?_jLvstBEQS^6FDp>2lg+y?m~_xPl*zO4bmKgf2nsk!JMWM-S8Drs2X<9p84F&CTa z$zBS+pugv%+O8at0opPkUbENyb0)^f++63O3V9sKwdGOjrt&nys`A>22T7lJyN_CK zt7_{o3S}TVbopUR{fDVJk%W_)nK#SjL7tWKf}>Gmu^vHwrvT z%ykCrjAQW&7Jw_?=iBsz>*e8`kqL+5Mct{j&Gz8MeTa}B3DR-BERqAsA-m4QL>uCBp#Y_qE8>VPj)K;>5%eKyCy~rpqFc> zUm>=`PC2BywG$M{jtv)k6PlcBWR+jl^7!|imraZYLh9tYjOLiSua3G}K(8t$Tnq$~ zNW87DyAwaJTR%=^JH(Ly`DApxBVtrYdR9{CqIf3EgL*m;rL3c7C$SGn=Y=%}F4H|X zhE%@@V4^2_9qB+p->^<+z2Ald=@5{zF)foB9&oQqlKemXzBj8W-q_7jJX z5+|=@MRh95{mD@56!$o}@68@tB{!HEIF`=rN(0V$BU)|8GnQc#H?_14VGIZx`{xn#<5@`-+GlmS^34-x1d%wOEk<|SLC&qLdrY+0N z41id)=!kn5h_k5+xd+;$+;g1fG&3x!A1cp2K^Sf#7_pPIPr#2{u=d89iCp7ZFJ-x{ zdxHBJJaU{H#@rT8W)OM@tY3q?pNwwmM~5;0nWJL1$F`&pP=cXFQ_djvvxf8Z5C4t=69CK zzLZxan(Wt_40e%@8R%44iF)6Mx8iVFykb6IaxYcsyJ?h%xH#+U=XrA9KkkXAqFyz6 zaBw`;ZfP0pOHKNE6UeZ5T0njB?vE|CdOH~|R;weFY}2deya7SJ0n=XInNsX3KNKa| zGm6!@y{TH`qTEeqB>ee_qjFd{UZnv2bCm!y*h*OaAt5*- zruep^{6pMlnv*i=%5nZ{5La!37FMYjdd*&oTu{ela<;plzKk6t=9|7qVa0xS3u0S@ zUA?fWeWK#B5a0b#(`z(zW7@)6;_+q_Q5V~amwTxKi_YTd0im}1k1z}4@oCax^Pv`R z9rF9grj>5bX3OF=DS*4RY-$l?)ntc4+qPFm**7;hi z!&EkhGf2qP$Tiq2Yh`!ebE;(M8(q<(Y5^*$Jb_d%@U({EuDIn-ArAh2dLk8RwYolD zt72QW1mVJpQI?t7Wv2c4wp_Vsn31Jl&vHcw+QG3pqMm~#xgFVN^|~v$WPYxfL>?Go z!G%`sDiA-`uww%F1;k3TrJtU^z=pWARkZDIt{Tnmt(s@7-cm1`nqpR$ilSr8PFI0S z$)+G)maq)*sZjU*it9%=jJGMxJodRrJthAmZ2{h;N-U=I_4LH^1u>2+ox}+L-!9M^ z(})6U;|hn*CfS}T0t*xAREB-G+Wb%DojTq#c`WqFa%0OLeZ|@oz5K9@uXRm9hjH%PsRY$jeErL~O7^$4B!fgXTa)7~rZ9B=eHLM9y+| zMnKrcqTZ2vCBWXtt!Og>KyB9Aa@jr_z35Lz7v8?7KF=^Kwmh)(r0%D|N|z|;%x%se zBuiB9?&$p`HS7%@cjX{DK6*R3W^MgL0PC8PEATJ}IlGD{ zI`r#SP56VC5W^zb%`Ja^gUmE=dbR@gRnV@9>8L8s+D%MV|2^tQC~JmZlV>_B9355I zOc9S^>_|PJv`e%D?K>!$X(Sw>!`2~d!sr2-92XiDg<2MKbiS&%jDcXR^o@rTg@m=p zpW3dk#V#2BE9$2sV%k;%^Fi+_A9klFW`G`jw;q;YTc`mK$4sRc)8vq|?__w;u`fJI z87YcAzhNskpy95Z`udK?Uk83x!X_{BDfn&nukps$f@P+MTr*5-?=py_EKA5QKe_}U zOTN!{`g1|9Fa7_PyBv&mogkaKmmXGUbR|E(AN^#E3J(@zIEnI>q@U*U5y|f=hGkx! zJIV3udBuMbL>KzaZSS*OzxL2uZ|l%g*;i8OZx)0R4l+Qmr#_}ju>{qjc?i4W)?6W`H$e2*|?RS^fH)+PZ47wvB%avG0osPIL);8 zm-RhWPC=2J**~@zKC}NUY^f$`$5*A zR&WWy`SYLf?BBYJ&oU&FHl5w4_-Y!!N2)%8!SnM3|uj~A&rnldanEEEzZy>;KcG?zWeyO5W7RX?j`8l zPJD=Gtn5`3h)w!*Z_7u8c+*3rP^(BpRwKS8v05tY|D))-|%8B%BdrA8ovRAf^o+#;!;~vr3EIoasDbVrp4*MJKj96g zo}wBf*R@ZkDX-cc_jm(%I&=AtV7zdTG2zJ?R|~YXLA^bbP)IsYK2)v6UAgqYmE?4h zfOLXPvo1SEqRHZ9}Z@frmw{PmrZmrVYWidS3(02evV@n$4yP58oYMbx7>8TUut>I7P)~LL=Mc;Ieu_OYSx24%w?^kgGF^ z(YQ0<8tr@T%{UTtUyWo)RwOuf<;KrWeSo{($vJixN-VU!mT0*@fAb7_szjiYBRd3= z2w75Bmam(s?5-3@`9 z-oWZBy>3v}?{k>=(O}eL-0C~3i0(L1e$>>XZ=Ak+F1-e72)qAa_Q&sHmQh#d13M7j za;0=YLJuixn^LobAcU3#q`{{zTv8Ig>5bQ#Ti5pc-)4iP&Sg2G3WDWGkBJc~U<~v& z54r2u|0_dYpLEQbt#plTD-B8>oatwJ&%0cJb>=GAi^?jjfn~4N>DDKKkn-CP^x0 z*Rh{WFAl0f`!#R%bhv1qz@JL#sJ^z7wu33MoITwIy~O}K8Q7Wvm9r#SE+rm zxN13xS|9Kk)l%v8d#V=mAM)QY#o9L*V%#>-UVTKa?Fma)#;Z*oq22cDQYK4o#UDt} z*keqOi-`W+e&)~Gf<8L9S7;!a8MkDZqQ0vQzWv2s2d;NX8{DS$5jTA4)!5Zc`~;$qZA$Po3zOM8LO&Eq=lJChx=s zugzqb(yacU+*#<2j5WcqIC}n1GRKQMvp@83#y5pAN;*Z$W&_Uv$Pb8BvSjodxU>*e zZ&Svt)OzK&4|*;3q_YvpMVSojO(>k<$YOnx5O{>%tnV6r2WI8p)MUJ(iG>YHw14?a z*1jks>pDAKS!+n9Z}RCv6}tY$;z55Pc5Bk9q~>D_Lnp|5juOXP{5q=)2JL#XE<_W2+iv#n zHGGTH1P;kJi>3thKCre@1_VSj=Zr;fv2xw2qz0%m@H}ctj+RLBmV@X?1wSCxT`ChRk z%jesd1to$^AI*nEDl;B0hMS8QtR!tUC9ewFN}+ZSG^u!2vi)cuj^nzsxBK1SuhOyn zcgkvqSIb*&;$k;crz`u5!Lhui!N+xZg@`qRg{X9r)MzVDPdlf~w%fCr&9`nfk*jQW zd$hAn3mUo*UZSC%=_|@d!Nr>_wErn^p&QVh3d-n^yaz${2p?j-Tz6lNB5mwrTq9Oyjz7csju5(Tb_xy)yTgWl~zP zlcnzFX)=GiZ<&40bDxhO)s{GY52?oRn(&xSaAuA}QXd7{))cV|>+-C;K}4b;s~4cD zHa@rwF?K)QgtAC#{OlJk{zo+fwad2q4$U9+?t@$Tvln^EX1M*FS=B!VdW;ev zWXHvo|8KB)lzC_l(1YdY@;k%f`>-Ugp2#@+S{pL8F%6ADj4WOuJIKWrOsr4QcmDpk z_%nrkDFKA7Q=b_MwCiSzW6@VBOH-D6Z=8Kk-3&8JMT%~Mi;?tHoCQHeb7u!Aj1$X{gxO~l_I=@;bH0?K411f=A30Zlk)X?u<(>Xp zsH%<&GASMzdEe*S+vUk(q=A@><0Jp0QTxI5T@58E8~HUr4JV000Eul=7z*R0gipE4 z#A(4o6;JogtdHFc{2$oxFb8w$gP0p-d~o@R%!Q!(Jm!8UC-gN zZQHwsD$`I-x%UeNlAitw%76dWP%%`IKQ$LVl31^zXv*7IzV$jF zy5i-yqbieV*DL$=$O1s{V^bB~|NBQY@pQOBQP}l>bd34h*TO%SYHJr(#_m{$d;STa z_Df6tH>z(ke;3l&%t8(EQcNoFe|TyQls{^ftG1hKYviNdV!lS?sp%MKzq=i1T3x*P zHu{Z_6K`S$Tijd192}l{J_V(A7qK|Z$Fx11m9a*V()IZQPLJnQa$~eXU}Oau{@)(Z z1EMpCa|8tZ`WxZhfh}$RMORVF)J$5egK0S=hH<7nwHomINOw=fO zx^Yo)uP@1a#!-w3E^;d?(6pg9Z!fS`z=++I>Pc~lyZ>m+sxc_i2J{j4Tad=2MtyUF zL8k^Ym@%eo-cL3k(O?C+Hx*8&`}eWP0Xnmmn{Hllx5ISPnuHxV9*46RtT0<&{k6Q{ zO%8`2q8A=CyUJLy&jmvJjEShxM}4hHZ16_TQ&&UO0&_c8>52F%V(HT0Sb^db(2=#G zX^pwok$Xs%$04-9PG~6T-3sQ9{&gSSs{!PKrIA8s21s^Atox8l=AaK>Lfb6Y&KD>u zOY-p$K0uTE0wlX<`0&{!VHHq{cfcPD&$TMLe)RSYf7QMp71&8-N2Cy&mieq;9=$j2v5q%s+``d1n z#^=@W)Jn~f<{o8c$ot#F)_jArFAT40js1*FTl&S^$cLxx&GRJN#%SMnngb|B+>>zY zfgDPf@w@R%K`-gN=XsGBCSz%he>6rJ<(aG0!tlK}MO_oyAm$;gr?bp&3u6Acxdo!r z9d-8|h~Q*K{BG5JUQaZ|ugKybIZuMR$mKCKTQZOv(=StN!q(G?USqRuMHeZ;O$`Lc zS$A;2?$hNl5da};7VcMP`(G(<_P4f?i;p*Q+434Fch1?&Y@rQ~P;3=x#KyLk=p~BQ zh!x!8XusROl#~0T?JG$Qjv%1ozP{!dadC66Q1oE%TeJ=vZ8vv3yi0@$N?v|8eU8Vq z=25P%?hD73!=(to*n8?$e?=M`=M|K0R3H_Jv!A9k;Qu1X zaXFzKE7U-GW-++<+XCEb$7PLdda^v*E=#t$Z80qYUj2$|cWYm&8ks4=x8kPp2q(Kw z-|W6T5h_5MlkJEf>k^$O+Z|D^M|Q{Pv9}Mr4T=O0cV_O1+(LaKt4l{*O&uuHPR!i! zJvCBgaOvPvM??d|f!t=}kmg>h&L3NqWdH;?@cA<%MIm-EpKE6>Z2nL7sR~=T0Iz=C zpMS$J{W6cCaA_V$MYJbiO@~8iPEXF>{{azasjIdDWta`FKy3I+-|Ch&TR8Rf?WG@y z9p_WW7X@3B=dCH`DFSSFg|EuYr4F}P4Q;|$rM0Fy`aTEd9MtMa&3uy(|9cMkpG9@wu>M%`#L2dT66cn>v|HVJYob*}si06q}# zvNoW)kzz9UVzjN)O&`;oxpLAl`=D!zvQ|F@{^)&`{MHq{fq=ze5d!5;(_qnY!CBnB zF5WH_u}S`c?ketuMJFaqJ=_!rrzeicG&I#$v59!!!SK&dwNr*T&RL?rNk&Tm_&$5T zYM)t0yPP%SsO}$zWe?76e>Syu;yJlYfap7Ld%A>)bN@)h6$t@XPJ@l1)pjmK$}|Ot z8Mr_2C08Po5>|_z8Cb5%`#QjqecAo@ca|w`-eA`=d+Lx!P^U){?3&9+OgAJBlsG5; zowZRO{?A+VuEyJy_3Nl7nUox55DpbnR}Y-a;ZHh$bvtw}yUxaYk-C=@PQkqk;Fd(K zDJrZ(hM&EbGaC7HXDRFmT|0zGAX>7g+#mQvhB6!#4<0H%d+=IH+wCjG#-B~=Ltcq# z=TaP7SVClxO4-BsR@bAOC$&Uw5A4vaQtvEB)JB@uV!w&kFz}PzSmxO`SU6`G@jbbW zIH&THpOaG=YdA6 z!T#MLd^6$`6_YFo@*s99ZgvA-yhB7C~=zzWHt%M9f1o1H;8tw z7i5!Z1ozB8njxFuN-EOhpl?By>JBunyhyAUtbM5u0qmOQUjHstQ*S;Qi>=6P2gzY| zN&Hs*xop^}u7Jm;2mKl&xxZ@f);sn8%KgI=nh4DVO=sbhSg_96IK=$V^s@JxCK%WSktM@H_FKvLZJVFF=r;60U)$ zFDIGaM1x zW?al=qdmq!m@O56W5rI`vQN;cY%X^yUfLHQ+m0C{$y)VNDkU)ewu`Q`spPGxnS5n0 zn5E>0$Iq}fFw%-?3P0cISIq8>e>5HHGRcj;tj95Pg3I1^^4#3Z)8k0Hw!UY2nA+gl zi{yOtZ*jZj*>7%gdhYOWSXZfnVB1QY(3l<0sPL~&K})(2>u%L3FnIPe~7?YTq@$)r#HWYcg;*(`$dN97Q}yrktVc z%!TJwZIFzq22U8A$l=ttji$Z;)o&MV)ODL=6RS<5I$=WuS!2#4|IxJR1sC>LC5;|xzmU@wM{1WST9sgQ}=+oe~ z50gf}3|OOtbBek+XZ7ak)0{H6H3`f<-!D~wFHBAmjbEJ3TT9~8B{UwX!%vrik*9%g z9$9y_H=#?v&|mg=vbWO(QAidV zE3aswbN^4dlPB%O>{sF$0j2?$CK~Mz^%QUW+a_7c8JbOnkU78vckJ@+tPO>iY8yKB zm|1^WS4X&qnce*w&*VnmFUDGVf{rQU{4> zqEZC|!k-dB@AeP;d~QD0pQax#zEDT}Gz+Nj($v49GVwdfjyqHsoW{|@fSU|)!13LE ziH$UApf!ug0e|!wXLdaLo-ko(g~I&2@kIFG;g{tCXy$=Rr=83q{J*g=or#qi%hIKN z;{+s|waI-^=zQFZz!%vg!9YB_Tk@CkS2ojJ=0C?6TM8^nk1Gb@<$1u#(4l{$6emd; z*{UC~YXM%gX-}eg{TM>MM~if63#Tz_Kv};#np5@sxdo;pZpnI~dirGe!9m0Fez~q| z8uI9Kg+$iQlZ~P{3gFBrnsoX|aXO8X4H z>E+LLWY4L{N(bn3CIgR?vZ2h&>ne{lZ4{?wkw2~!@U688RHAH4VCye&;E3NYDv-sg z2G;$iO)i@=5sxVkz-KD5c#oJh%g}}7+?bS>`-$V;Z$BR90Dc18RaFI8>BXgnd}2K{ zp6W&JYqH)Jjx7k7(zWG);JaQ*pCUb%&!&2e^9CbwOXasrR*O*sl0?yhITvK@RFq_5 z+si1SK-42O@+atue-B@hMVTnM)b(hH!YEg4;exDDEh@HXR&ZxfIh|T#NLYu7+wxt# za6KZ2W)~Y(ol!hMe>4XJmj*|A?IaB>%%{lk+kKE0(2*ATKJiyyjg#NXQ8|;Yk(b1r zR|8u9-1uSN;0+pR30Y--q`i$~o^TWRsjPrwB}p?J)u$0JJM@HT@ntc<0$NKjHhk=D zk(wmT%NELdgxwyvD16xjgl{QE7Y<_D4quYf6%IjVU`F8bWXe+N1sRa!mChr;t;!`gh0ysz<=o>l5~ zCs6VYbd__bn`$s;+t-Y%>3H~aArzQWCdK+?WqS1=6YUM%I7c3yO}t|jRpAaQ3vUOR^gWM*c7uY z#D=`Pk`0uaV3S!CDfu1qp~6AqlZPn`XE;xr&_3ESww$|Dvb@ORGUiITS}#}kNXnai z=_3T82t9>{&a^QWT2`8fk`!cGcaRae+RAl?xWMCLrM?T+s^e`m>jsi3fAaBK!hL(5 z#2d76EE(vb_6S>&>G0ld2|G@UPdVMrT!36GDjZnD5K9G;vD_9G13}&gI9S6MLLbD1 zBnaJ?3PZj z?=S{I&sKYn^NeK5O_gMk4i4=dp*smK#&3>_>oQv($p)IdUb2}Q9L#C44)-3-WArv0 zd0`e=pbAWh8*|=>g8;tE-l~&X`Y4)a-BnZ0+s0(7Qm2ad;ixK=fB;@!InvL+Z~d)_ zPgQM&Yshj+<~7UGl4otZi(DHeg|_-8y`YQ>dsQ92p;U`yDOmLUs~1LxR_FJ0U1BVK zxy!GbP$>alWm@xw{*EvB4;6H4o7x}ci!|a|)n)SDtlt87Dl?B?G7JDHF_&B_6VvD7 zeIX(g9YH=S%1Np5`db^(XP6{?kG{ix_AMN)q3Ko%ioOd7p1?lOQ|BO z*&+doNddJ$FelJ&RF9PZeaE$QnR~m}P2ER<|8vG=_~&N+RF=L@jEBx?Z2QbIZZf^# zy7ivU$N#z$7!DZjmTkaj5qARW=-sYNrwPjUsWjtS=|js$C3hbNWV)7MJ9ynYX@YD^ zo>N^Wo4H-Br$!gKlzSU;&dz0KA9w!il)uo_*%JfXgX7HmQf5odz8k#P`b|!ytF)0e zV&;3Pq#*4X84S_ME49cr>?{}QSb|ZKb?>9>;emvnX_2NXRb^r9HB40xwr&od?5K3;P>p_ z^Wgqc(70*F8{HN^Kz6*%5%@WUWif@Qh@DAytA@`2{oSRlBzIO$q14c}h6tHPd$+Il zb8#rDD>aEM-^xK<6ehXm?m970ztpLV&ys_SQM(_tUI{Oyf~MT15@XJq5~~xB&EA2Y zT@d!IihAY-C9ak8=!~+<(5*9SpW>P%0ZuuOTZU(3z&EE$I)4?;+W~M0wjV_O$2CZR z3pD)^z^ImKnXY&V>MHS-B%{qRQ(nGTn9jxMf4rjrSl9L5*c^PDcVKbyepKXkcF#f6 z9|OV~n&twu@?i0lsKS~1tNkg`xe)`d7=DDS+IbXDT3m!=cMk^!LABkAddj-tdjr5tSbA=AVH59enuT#w zup;l7>+iTAYOoS$T+C1@$RV0gg=8EWsX3~SE{LZ6x!r5;l0Guz=TqRZz?u<7F=1Cb zY00k-OrKj#x`!K~y-t~`JRg_ito!&fB)$sO)ZTmMTk(2rKm6h5b{nLr6dYNpnq{j0 zRqjMfqxo*oCIgos-5kI(&sza^^|;m1V1oNj`u(VGd%Llz7}DX%!&JB0Ra1F5nqeEp zu=zr!Sk7NxIHU1;92FmQ{D0&6hSFaz9<=PXBUMwu`Xp= zvqZ$Jn|{lwxOQITQm(a1SSJ--ZE2ZkHqcFHVZP#a58W5O$QvIy06ZNF-2@m9I6*dJpde0 zg_@uB&Vtng6_5nhc$_4bziBb>8*NIGs3~dujmx>Y|GPI%35M&)F5eLRBC8K!EKdl`{N-5`~Fs zmZ#?(b!+Rk!vzaHjbZaH=zCd(Q3xd046Hlz8j+)1f228Dr*|p^0!2PK)JRF6eEOsT z8MM4m`9JzRj_lO(=+MpOGJBdw3D|rQ_K)W4)Ne50AB~5{?E11>O&an>3(VV_xe=Oh zk7KL9n_0FH+rM0lP7|tqzGBQ=+NxyK<0$_tHjP@GMgZ@18Tl9KfJTJ8$AtQS-@KhZnUwfB5wO(;`9A=09x!hPhzi8#tVMYH%u)Og+?FtVSqIrY4b3cv;!^#c}j|*r3yEKiN@{q&x0c z%jfiO+?;I;R-DJ$+s)fH72kEX7@Liytzt~$^)K5e=MAVhyWuW9R4TWwi^&;xj!$8P4w#3X4i}}YDxXS1J6%==7wOEXWOw`KP0hf?D8vp>}(3&E^SLxygWKt z%}<6$PrK1M0F-FFG^Hk*`OzS5|AFU!3$qz5cAj`Vl#cz{UC%(12T##_^?-scPtkK8;~AO6GsoNlOT+u9cBM$#6Ml)T--;e&Afbi;X2<&54UY6 zg1>8vopi1J@@TyQmc!wQ<${Xp_@%7l8{YD*!%wN&TzENJ8`Vj4j^I`})&I^{${e z;PL6l1rEXAvr@UI0qB+-ucr^*Rxh_#gIe|CU8(3eJK_$U8VXp`HhbqI;H=>c;svuJ zn)g}J!BF}HNR))TWPa=ihEv@g8uYIzVp;JfMKQfE2MDWfe)#6*RE{PeXj2I`S7!PE zIOn6}p97eE{n}RRgt|x$9F}^O&S}1ONE=VN`y~!FCkd&ejK`c^{b@)mKVaL&7$fJ< z57=$zq)2RmIc|fR2Ye%ciG*xg-!0-5(IH4HQ*{u(M5;r-iuwq01C`;r5ziX#{NF!_ zoHD-njC4;u^60&*I#b`o;<|42Zv5rD;6oT~#_rJQfn*3jJa_x{XYc~~!B0w6iSW|v zHb}|&J#hG~RIOLgZzk_JWH+<`rm{RCe5L1lhpcI3J88jXOI(^{QLcMY4)El#Bh^&7 z9c;Pf2IYQ|uG6GZ%L>bv*Ck}t#3?MZS8g+3O~#3!a242bbI*qG1@)X(M6x-{=I{&} z5;c>z>4d*|n%e>6$onyji`CxudYAabF`2jC7BucN6ic}`F3p?6Zf3#Du1SR}pnv{y z5Ed~!!4KTTa5bUqVXlfLwo>^1iUi0_xJ7H7{dfg_NeKd)DtZHrbd}zCKwbKpPcp`p z4bb1yZzrUE?1~SCdJLT1CXct*L+~6@gekfror`P4CSP>5WpMM$m-XJ2gt1yU?S0P7 zDv~Do$Dgam_~*ObN;l1)+dP+_4Ki88(`}nI#eGv23oe!U^{<}D zWNx@P|2z?9skp7VL>O&UE40uo9YFe%eqLM7S=8B(sG!fff^EPooS#(q1_2>lWkZ%5$da)o1<+N7m3IApL_odoYK*F9it=WjGD)@KDGPaKG3WUb|ne? z?e?fjgWm(lWpGG@9_VC-AeZ8o+VY5PM**FKMV&?qz2k+D<;}3FN{jc_ zy_cT+TRqW}IaC-xFX4aO<-ch+sRpCS2>hWDwqp(|;x8Cv(nX{{0*LwH!`a z$qM)U_8LlOQ~9aX!-_XY=&GXqy!9#(Z7T7=k84XOnr~iYb`MVtasapJgZcIgQf69$ z53SQI+XLVbHssY4ue;LlTH8FcXRZyXqJK2SCLuS|rWxdihJ((bqkZC_>chy&&T*5F zshBXHK=?Fe5B#hA*0lb?m3e!6d&eTc$d&IpLZ-={Ww$~(jmC-!^u^Z&ML%39P>ffv zEF{L|H{BS4Nc||S;##0;e910)&G(NqRQ_8*$e23i8-*$vdTSqK->8kt zNJ$kk73*HBNYI+T-%pxnM&^J-wbCOtz!IqjQ_pIYP}T;bZJn(7sPYT|}# z^-kCqi^(8;u!QM!tjVZ&l?NXYh93R?O<4*9dSFG4i}N|3-1k7buS22`d$5L^__Y~ zZzvEy(JxL1niQ(OyhH35(z&R)NChvY@!ujI?jwLv=56)fu3{%^Yx0-u!h!k<{fs5#}(mWx-hBb!(VKrINM076X4quebGG;vE9C(>&Dquf(wXcZZKlAoZUo zT+1@+)X+FvKBd0T?O#WnT2YVxwn1`QP#!KJ_Ra~rSrov7z8)kzm%Ht@I+|=c zw#RAQpponds(s<0!AVUPhdYzZ5gmU;h>isI#b-p5`~jKI>u|SezZ0}yAQgykZy#RDs^?17EH2h+)+PkkA4G0Dr9zl-0rHvC!TT&$>1iF~$Yq?>sTLFOB;Lrrsf5{<}<&xr{L0ZTq4ZOHFsZ=n&_&w z-yD!#?p7G(T?P0dkEeK982zjY67|@9SZ108_0}R6r7&MoGI8{?>-9&nBW@F$v1v`z zj8Q9UXx+)<1`02&5g@yTw3AIRga;!Ib* z2*L9TOkEoQQAb~nvQnw$e2R6!`1OmqmzW->M|$2A$%fh>ExcRb1fRv?=45IulL|kb zc$cpwnq)l@=EggVA8AU@RhyP91RY*R&0a5OpSi5-eIK)5d|W=WYa-YIOVw0q_{QWA zv0&0*LP;GA$bIo{6{S;68a%DK7&FA}3M!H;NUyhJBQ9p)#SMG2jfuX(C6eiPx?%z6b#iVacXeuT7W%r9V+mB!Oc!V#?7l zOz`7Xa@lfkmJYmo_y}DBc2KdMqHtsv?#(OPTvB4obs1pK{b8?7ssooSh8t3Y`>u*b zyc(hQ;Ag%Ud8WFoa~?g(aT1Oq+`I(XSZ-%oWaGZiFxZD4@d>?(IU%~nGo4IK6jsYa zl`c?RfP;CV_Bs0megW7HK^cv+M61?TAzsz{X&#`n;7`Gx-G5s#y_^uw;d$-uTk9S= z`_vE#!cD2o%nKk>lPc&YK^Ap`aQorM+iR>k+i=aFToqauv*)EqMrB_8b-kiXwl7qu zU{AOhhX_LzI;Uo0fjuD`wBZpR$6_nE`w;vBG&X>Yf@`uPubvX^(l+x#Cv^Jpb#TG=?;`F;tnZ=84w++Gm`{tYnute{t!s zrHj;A>^~Yh1K1n{&xUjJUpn3SIJL-HM0(urV2fg#3%TqN!#c1DtK5IB#vJ_aY2K*l z37B(8x*uz7(h_dfvw}fmVgV)YaHM6H6DqtL0t_&6LhHM*?1VbC>NRgK(eYvuX%~ig-4-Sov^(pK1 z7XOaWByv)&uS_26&gB!3aYD7Uk;&Ds^;KwO&N4{0$WCchNyM+rod<59?Q{5O)U!|1 z8}@FAD=c|JKIl1cOnVKgcY%(=NQYg#j483?<9VSi_pJON;(GkNS8wnf$$5ISO_1n; zMX_(oFM{S)dDq`*)2@%!Nty*FXyW#EUQ(YfYJUoU9s5%z z7r5nZ?jkjCuQ60I-BtbE4?PULLS?4ukgriS19r>ezPs)bj8EN98ZKk?C-_^-wouc+ zo^#Xo@_=NM{di1o_<V}hW@eCL zJ)1j{%mNugG@(yd>_oWuO4DLV=9E6FZYna&r=5w69CNQ}{yDBjJ1&s4P#+Q>vs2o5 zq=0Ev_F!EllykpzkI4y6$X0c6{~NQu0>%$fM=O}9u!LqBTyB4F{;ek@c_4m3^~mGZ zEENNf1uw-fRs$nd`R2$v7x_{?IWL1yQUrE#a{}!N88qMSjGAV`f+HVa$SpH;4qBh? zq#a|{DxgbQ zvu5>2<(6Z^1ihYdGZF-3B~x|Oq1u!VAdf2F5OSXdq^F9zI{p3RA5AlSDcoJFpJ5*z z3$}$2@15>+Vw>?Aq?6D|H;Ht;jF6ZuGfaQva28t#>=OL`#b@wR{_I6z0cYohk;RCQ zk+)p-)t01L;Q{`SZ{95Vi>aoPB1vr2DFRb?oc{vTG1PIq*NTbujWt-aQd#~f`8z9B z{p@W(hy2}^d5+uJx?S?F7ENT7P8;Z9w_y$88f-17wtdHlI*OeaUur&Q(_te}PLghk zA9WJ@)ZDm!Jp?~Ek~wQ6QE9zSRm4@Y{6(3J2MsY+Khk70+b(TJ*sAltFe7(EI zA4EOR zY6=-`+aU15oG()Nm3%G>sb3Ikq?YS1H3=qS|B6hHhfWH64tiC65Z-e3Cf=Wf%fh8A z<~Pa&q-G#u&lh)d==~S3v?%2f+i}A2UtQBLF)1}i;n(38BZA>WD=r?f=Q`bln--Da zgVP5BZxxh%^04(E&~Q!M94m{Hd)jGwvikMrvJargae%8H*lxBV7s|We#iIo=*&w*s zg&uT0QgI9Ewn&GdBR{;zrZ9HmkhB4+DsKY>VJXk}R(!1%r7&veC+KX76lDzggBrLm z3yI2M&h2hm&CllVGklNsgqODjyMj;rAjAjA4oO;zZG)LjYq1|Uo;oHhSX#+*@w|*s zdlmd%wf3C$`w&%9Lbr4=Sw@KL9C1q5`s7Byi_N{`jhn*16u)os^=emI_9ZDW z*DHj?LH6HA##19iD&4tfynEhT~l4{x52Mj0RjFeU87`2f~5Xj z{?Asq_WMbWpfNDkdcxd0;BPvM|YqiIS;kj z1th&4Y99txVx9+s{vyCc4=rNzzHf^OuAKgH`O6!+4VpEfSCCPcK;v-NZ}2y@L|75B zTe`MwQC#kfmamcle18RDl1pHsaNI)D@h{?hSKe;vM|!%I&ea|DT0_J1e||Oq<@92Xn{!d4nX2@ERov~$fB(lam zmQZc&l>3~4J~f$^J5rhV=%hl_J?s9jZ0>tPgZFMfCK#ryHVq%6=5R{yD3?oryzlFj z=Y#N1Oe6Y#vF_A6vJ4#u`shXs85>^RoNdjcF0GfogI$mzb*AV1MQ(5OntJc2)=@WC z5x(Y_38MdgyEau95wYC`Qq&**PXKo3RD$8Jg39}UG}}-#-KHpOOxF|{KyJ9AN>XGMa)}*&DD39XJca9Zg0z43ykr> z2I`uS$pf$Y8Ce4Os3ypDxo&g_xEk1pic`d+6OK!cAnbFNfJ-3M4>{OJa!bQG9V~zG z8sZxH`9)?y(mxtQVuCN2*^_|r@)?s$Q#j}VWhG(?Jcqaypq^nUadN_rk8`K!L6WnN z>jC%^n0P-omI5L+_r0LTh%(PQ`A?oL+^f1rRdpRtty#9Zo+?oov4mXgVuECH_qkZK z7m~L*wn&7Z$ORz2=zH2d+J0}@Qid^Las!gNzM&v1IQwc@D9PHK-H1uz0jA&y>~3I) zy6}IvPon}{U;a2a)Aj|IeDv%fjV(+$19;WG6xZczZv@Eoj`HIH!JxGZZ;DDRH9u7q zM6Tb?>1umB`NWlD4kneEi%f)H#d3veJh->w^&kCbr){-7(X51t0~N@KGx>gnVoa|5 z`e+%0<@ow8VX?rH+mTubGU@K8=v*-{H1X)wd$;)BK{hZ%Hecdpq|6rb8WoIr5C&|r_qaeH6 zxE2x3b{?om0;nMw=2Lg011-X*gbaV@YeV8s;V6Vg9nUqt@67Lb5pWL8>Uf@fme;C| zh7GFh?Fr5dJx6UCnuXOPU3#g(4S=sRXxShoTm$!Aq*UXc1!rJxGV8BEYP8VBqFni% zK_E3CM@!<)xTFZlWZNCJizY2ZP%(|qtkwS|h8)(r#FAuXot7zF1Y+jgB42H?-koj@ zX(A(elm}RyVcwT7Cg8~T!c*k}6cYN{*tIO|P+@s)HrqRJ_QN31WN6crzWVBZH;qrZ zn4|PvHsL(a)8y+>VKk=n$1~#+h+fut6neD2R!_|@ZFsKoav(>P!yJ>UjgE`faBX+xc#}C*BFE7k4Rx zW4aQNt2YndScA@}J9cR0bhgq-l0_<5rnkurxG&+l(qLjCmD*1&ZWbWvdxaLC+!mE@ zp#`T#PPF3WhX5OC&}*`v-^`Fo={FJ+lojc{-388Hth=v7deyQ<<)Wh9Kp4O0AMd%3l8e4nK zVZy|g!-xx2m8v!M!-*t`MaQ8LJOO+9gvqq8MPm}9xQBVks~H9E&H9NAfy$KEKqFV)o~kzYHbN*ow*Ku%(?<}(zaaHv<0=>)NOHrYG2B2XOa|yE8&`TGp2QH zQGYaMzcay08wGPFR$=>kz!*cgEupWKaJNrdyro`AP=I3|pznp+j-iOwK`w_pkH6zC zn`Q$x4g7ym5lZcjI<*j72#htbc^Dz+92md+IaiTetUQUDu;`HCMT{X?GVl)md2F8) zefTLWqQ&`7OVN;AT^jfs3<%y~D@Y3Mc7RpWx0hx*NxtQTn;Pp<(Kx5kHPEUU9YvO$g>_dGy*T)KL zB4!+)raMLbz6TEr?^pI4gx{nueVcT3obCN3yF-p!{0g-07m z>&0q0@~!5RN5sMXPGx$+$+!9%El58`^G=mIebm!zxAbLMlbJN@q*em zE&W(zQnp)CK=2`k6dit>-VeX?s3<+*;Zb z^jY6HP3eOmjq)SF3Tq)%Sv(ZDZD=eS*u z52rqW2}X9Jkp8`<^GfXAkBBsfy`rB7}O8Cozm*N0L4XO77wa z^ifQVnenBbNNk*Swe!GI6!Y^~9=%>?=7$gXyjHHoJggD>dM83K<$f=hT(LtZ*NMVW zaY;|Zps4p&0(ys8?uS9>5x5w39oc#LXfZ~;#iN+G%jE1K^iJ4Vp7G$|7JpXI>pH3> zNWk&E^3D9VdSH)lP*jNgO>D$Sjy!O*J*!-asw(dTxe8a0wdj4fcxINJ_oC^+`vxw4 z=L>Zw2my!_E@Z1XUeKH4y0`aQfC3oP1a~WxPCQsRRxHL=Yhh9w4)TtZ<|vFjBx^($ zLb`>h`Ng%4|9R`r~xN$H*K z`$oBQu^qDl`-Sn#(h|9=pC%;SMt+4#4eVnM$fAW*l!gBGMa6M3KhqwE&b}7oPxs-D zs)L6*ZgKU)?Cl0H)}552S7!#RS$G-G-%_AOlb3%ElsV<(?zVs3$b|JtMrX3dix8$F zj+?V34I0<9@w}MiEn_e1a@^va(n&61GEp*88M>SeHLOZhPQE@6VZ?L&`;{x2K%KLq zoezOVmtBf75B5xc{?yy@>!y!%Q+>7*go*?^51}a6Yh|r(5z?ydXkn857WHO+IjXp@ zM10~gAl{#(MHz-3e4qL&Il<-KXt?HU9Sj{5KvB=oTpoM0+xztQ4c_xW(~&ak%=Q}l z0Z_Nx?~-qSYH^U8+kwjgX}jlk$UEGP&iOAB6fgaZlN??jZ3<|6NH(Yo*|F%EEs1?! z>fvrJbp%a5i&+w$@AoU&+%)^~oZwZDGSXqsvM<$eW31p{M}b1cT+ZuC%B*=7bf7t} zZzOA)<|G#3KVS)C>gPY>0Ys3oa61q7z_;N*F zLB#~`&2uQVy9Qv+-H}Z#Q-3G|z8qg}pDF)zM?`)^d+}a&F*U>#a58^oZ753P7Mv~-^wQXX=nlC zGNQQKnU)b*xr8uTf#4o6un|ma3X{%ZvO6m*PYNFUkIZjWNE9};9ZIEMsn2Wgc`5@4 z5$lcmA4TWk2=)KRagvmoL}Xk=Ss`R3T#=m}XJ7Wp3fXs+P}YU06ql@I9NF{i%gAMO z_R2i&jC0)h{yx7y;B!9rxsTWT{eHflug6ozL1NF^HN?Sx&mnXf>m*}S1y{x8on9?d zSxL>5X_1MNeC#IUFO$W#mys8Bgnv}O3S5L3EDItEK&p-kuYx##PqFDJC5{B9^;N6Y-L%i)2?lR2{Pc=GCd~lna>sa z6YlA$)N`V9qZvKHDXe9;FRrHAw&j$57raR4LHdn4oOnG?8l6oYH+_=n;Wm05Ec?)P zZ__U0a&+vAyJuFHAs$kkZ2XTJ@IF42`snHeFAfX`lVC`AX>Sp)Yv?yCXPwv1;No zd8KH}Jmpu+N5+`gP}CfH+RiKPgCh6w$^&VlDVP>YvaLBr)zY%LQnElSDE50wd*uyuk9^;G04U*`aME98rvIOahU#bA{mlEHW8Yx)9_tr zYezhaLaVOBPRP-SAMs~GkMhY93t5ez%=r#HZ75+?4{lZeYw$C=2je38 z5ShM0+~(l&y%_=*nU^J75e1hv5p*8fnPUS_)rX-xdJQC|8)X{{9Brb$*4maMhca45 z0ecwsFG1vTxIVm^WBna?F%Nnvpv9c>h?cr-OqlpKtnJI kAye<^r0wX=15rI^ag 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 z %Ar;l4}1Hjs&N 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%0A0*rq6q4idEU>Gn6-(71g8wJ7Y|@clfq( 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 info["MAX_VALUE"]: - value = info["MAX_VALUE"] - elif value < info["MIN_VALUE"]: - value = info["MIN_VALUE"] - self.write(value) - print("write: {}".format(value)) - - def __del__(self): - self.fd.close() - -pass diff --git a/update/beta/OpenScan.py b/update/beta/OpenScan.py index 0ebce0d..681c78d 100644 --- a/update/beta/OpenScan.py +++ b/update/beta/OpenScan.py @@ -1,5 +1,6 @@ basepath = '/home/pi/OpenScan/' from os.path import isfile +import os def load_bool(name): filename = basepath+'settings/'+name @@ -13,6 +14,74 @@ def load_bool(name): value = False return value +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + + return True + + def load_str(name): filename = basepath+'settings/'+name if not isfile(filename): @@ -70,7 +139,6 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): from time import sleep from math import cos msg = {'cmd':'set'} - camera('/ping', msg) GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) @@ -97,7 +165,14 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): step_count=-step_count for x in range(step_count): if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: - break + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + GPIO.output(steppin, GPIO.HIGH) if x<=ramp and x<=step_count/2: delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) @@ -114,7 +189,6 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): def ringlight(number,state): import RPi.GPIO as GPIO msg = {'cmd':'set'} - camera('/ping', msg) pin = load_int('pin_ringlight' + str(number)) GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) @@ -201,3 +275,42 @@ def create_coordinates(angle_min, angle_max,point_count): point_count=point_count+1 return filtered + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/beta/config.txt b/update/beta/config.txt old mode 100644 new mode 100755 index ce06bd8..cc525ae --- a/update/beta/config.txt +++ b/update/beta/config.txt @@ -2,10 +2,8 @@ # http://rpf.io/configtxt # Some settings may impact device functionality. See link above for details - # uncomment if you get no picture on HDMI for a default "safe" mode #hdmi_safe=1 -hdmi_blanking=2 # uncomment the following to adjust overscan. Use positive numbers if console # goes off screen, and negative if there is too much border @@ -55,13 +53,13 @@ hdmi_blanking=2 dtparam=audio=on # Automatically load overlays for detected cameras -camera_auto_detect=0 +camera_auto_detect=1 # Automatically load overlays for detected DSI displays display_auto_detect=1 # Enable DRM VC4 V3D driver -#dtoverlay=vc4-kms-v3d +dtoverlay=vc4-kms-v3d max_framebuffers=2 # Disable compensation for displays with overscan @@ -73,13 +71,15 @@ disable_overscan=1 # (e.g. for USB device mode) or if USB support is not required. otg_mode=1 +[all] + [pi4] # Run as fast as firmware / board allows arm_boost=1 [all] - camera_auto_detect=0 gpu_mem=256 dtoverlay=vc4-fkms-v3d -dtoverlay=imx519,media-controller=1 +dtoverlay=imx519 +#dtoverlay=imx519,media-controller=1 diff --git a/update/beta/fla.py b/update/beta/fla.py index 5fe4649..57f4660 100644 --- a/update/beta/fla.py +++ b/update/beta/fla.py @@ -1,12 +1,17 @@ -from flask import Flask, make_response, jsonify, request, abort -from PIL import Image -import gphoto2 as gp +from flask import Flask, make_response, jsonify, request, abort, redirect +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time import shutil from OpenScan import load_int, load_float, load_bool, ringlight import RPi.GPIO as GPIO from math import sqrt -import os +import os +import math +from skimage import io, feature, color, transform +import numpy as np +from scipy import ndimage +import socket GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) @@ -15,134 +20,331 @@ basedir = '/home/pi/OpenScan/' timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + ################################################################################################################### @app.route('/shutdown', methods=['get']) def shutdown(): - delay = 0.1 - ringlight(2,False) - - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) - os.system('shutdown -h now') + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + os.system('shutdown -h now') + + else: + return redirect("http://" + hostname, code=302) ################################################################################################################### @app.route('/reboot', methods=['get']) def reboot(): - delay = 0.1 - ringlight(2,False) + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + + os.system('reboot -h') + else: + return redirect("http://" + hostname, code=302) +################################################################################################################### + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') - os.system('reboot -h') + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wrong aspect ratio! +# preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}) + preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}, controls ={"FrameDurationLimits": (1, 1000000)}) + +# preview_config = picam2.create_preview_configuration(main={"size": (2328, 1748)}) + capture_config = picam2.create_still_configuration(controls ={"FrameDurationLimits": (1, 1000000)}) + picam2.configure(preview_config) + picam2.controls.AnalogueGain = 1.0 + picam2.start() + return ({}, 200) + +################################################################################################################### +@app.route('/picam2_take_photo', methods=['get']) +def picam2_take_photo(): + starttime = time() + + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + img = picam2.capture_image() + + if cam_mode !=1: + img = img.convert('RGB') + w,h = img.size + + if cropx != 0 or cropy != 0: + img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) + + if rotation == 90: + img = img.transpose(Image.ROTATE_90) + elif rotation == 180: + img= img.transpose(Image.ROTATE_180) + elif rotation == 270: + img= img.transpose(Image.ROTATE_270) + + if load_bool("cam_mask"): + if cam_mode == 1: + downscale = 0.045*1.4 + else: + downscale = 0.1*1.4 + img = create_mask(img, downscale) + + if load_bool("cam_features") and not load_bool("cam_sharparea"): + img = plot_orb_keypoints(img) + + if load_bool("cam_sharparea") and not load_bool("cam_features"): + img2 = highlight_sharpest_areas(img) + img = overlay_mask(img, img2) + + if cam_mode != 1 and not load_bool("cam_sharparea") and not load_bool("cam_features"): + img = add_histo(img) + + img.save("/home/pi/OpenScan/tmp2/preview.jpg", quality=load_int('cam_jpeg_quality')) + print("total " + str(int(1000*(time()-starttime))) + "ms") + starttime = time() + + return ({}, 200) ################################################################################################################### -@app.route('/ping', methods=['get']) -def ping(): - global timer - cmd = str(request.args.get('cmd')) - if cmd == 'set': - timer = time() - inactive = time() - timer - return ({'inactive':inactive}, 200) +@app.route('/picam2_focus', methods=['get']) +def picam2_focus(): + focus = float(request.args.get('focus')) + picam2.set_controls({"AfMode": 0, "LensPosition": focus}) + return ({}, 200) ################################################################################################################### -@app.route('/gphoto_init', methods=['get']) -def gphoto_init(): - global camera - camera = gp.Camera() - camera.init() +@app.route('/picam2_af1', methods=['get']) +def picam2_af1(): + from libcamera import controls + + picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0, "AfRange":controls.AfRangeEnum.Macro}) return ({}, 200) ################################################################################################################### -@app.route('/gphoto_preview', methods=['get']) -def gphoto_preview(): - filepath = str(request.args.get('filepath')) - camera_file = gp.gp_camera_capture_preview(camera)[1] - target = basedir + filepath - camera_file.save(target) +@app.route('/picam2_af2', methods=['get']) +def picam2_af2(): + picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0}) return ({}, 200) + ################################################################################################################### -@app.route('/gphoto_capture', methods=['get']) -def gphoto_capture(): - filepath = str(request.args.get('filepath')) - file_path = camera.capture(gp.GP_CAPTURE_IMAGE) - camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) - camera_file.save(basedir + filepath) +@app.route('/picam2_exposure', methods=['get']) +def picam2_exposure(): + exposure = int(request.args.get('exposure')) + picam2.controls.AnalogueGain = 1.0 + picam2.controls.ExposureTime = exposure return ({}, 200) ################################################################################################################### -@app.route('/gphoto_test', methods=['get']) -def gphoto_test(): - text = camera.get_summary() +@app.route('/picam2_contrast', methods=['get']) +def picam2_contrast(): + contrast = float(request.args.get('contrast')) + picam2.controls.Contrast = contrast return ({}, 200) ################################################################################################################### -@app.route('/gphoto_exit', methods=['get']) -def gphoto_exit(): - global camera - camera.exit() +@app.route('/picam2_saturation', methods=['get']) +def picam2_saturation(): + saturation = float(request.args.get('saturation')) + picam2.controls.Saturation = saturation return ({}, 200) ################################################################################################################### -@app.route('/crop', methods=['get']) -def crop(): - output_downscale = load_bool('cam_output_downscale') - output_resolution = load_int('cam_output_resolution') - preview_resolution = load_int('cam_preview_resolution') - filepath_in = basedir + str(request.args.get('filepath_in')) - filepath_out = basedir + str(request.args.get('filepath_out')) - cropx = int(request.args.get('cropx'))/200 - cropy = int(request.args.get('cropy'))/200 - rotation = int(request.args.get('rotation')) - preview = str(request.args.get('preview')) - downscale = 1 - - with Image.open(filepath_in) as img: - w,h = img.size - if cropx != 0 or cropy != 0: - img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) - if rotation == 90: - img = img.transpose(Image.ROTATE_90) - elif rotation == 180: - img= img.transpose(Image.ROTATE_180) - elif rotation == 270: - img= img.transpose(Image.ROTATE_270) - - if preview == "True": - w,h = img.size - factor = (w*h)/preview_resolution - if factor > 1: - img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) - - elif output_downscale == True: - w,h = img.size - factor = (w*h)/output_resolution - if factor > 1: - img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) - - img.save(filepath_out, quality=95, subsampling=0) - +@app.route('/picam2_switch_mode', methods=['get']) +def picam2_switch_mode(): + global cam_mode + cam_mode = int(request.args.get('mode')) + if cam_mode == 1: + picam2.switch_mode(capture_config) + else: + picam2.switch_mode(preview_config) return ({}, 200) - ################################################################################################################### -@app.route('/external_capture', methods=['get']) -def external_capture(): - pin = load_int('pin_external') - delay_before = load_float('cam_delay_before') - timeout = load_float('cam_timeout')/1000 - delay_after = load_float('cam_delay_after') - GPIO.setup(pin, GPIO.OUT) - GPIO.output(pin, GPIO.LOW) - sleep(delay_before) - GPIO.output(pin, GPIO.HIGH) - sleep(timeout) - GPIO.output(pin, GPIO.LOW) - sleep(delay_after) +@app.route('/picam2_show_mode', methods=['get']) +def picam2_show_mode(): + global cam_mode + return({"mode":cam_mode},200) +################################################################################################################### +@app.route('/picam2_af', methods=['get']) +def picam2_af(): + picam2.set_controls({"AfMode": 1 ,"AfTrigger": 0}) # --> wait 3-5s return ({}, 200) - - +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') if __name__ == '__main__': # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) diff --git a/update/beta/flows.json b/update/beta/flows.json.tmpl similarity index 59% rename from update/beta/flows.json rename to update/beta/flows.json.tmpl index f16c4fb..20fb35f 100644 --- a/update/beta/flows.json +++ b/update/beta/flows.json.tmpl @@ -1,55 +1,66 @@ [ { - "id": "829d803b6033a693", + "id": "e6f4d02efb300ea9", "type": "tab", - "label": "HOME", + "label": "Init", "disabled": false, "info": "", "env": [] }, { - "id": "1613373abaf77a2c", + "id": "481edaf6db5a7a54", "type": "tab", - "label": "SCAN", + "label": "Scan", "disabled": false, "info": "", "env": [] }, { - "id": "4981d84ef1a366d1", + "id": "80a3942785a26c29", "type": "tab", - "label": "Files&Cloud", + "label": "Files", "disabled": false, "info": "", "env": [] }, { - "id": "017bd4e4a428bee5", + "id": "e43a27722b508115", "type": "tab", - "label": "SETTINGS", + "label": "Settings", "disabled": false, "info": "", "env": [] }, { - "id": "c8e7ecb5849edb9a", + "id": "a5557543ccff5889", "type": "tab", - "label": "UPDATE", + "label": "Update", "disabled": false, "info": "", "env": [] }, { - "id": "b3150b13e34b1fe8", + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", "type": "ui_tab", - "name": "OpenScan", + "name": "Scan", "icon": "dashboard", - "order": 1, + "order": 2, "disabled": false, "hidden": false }, { - "id": "b6e9c2df6b28ff66", + "id": "5c06cb6bcc371ee6", "type": "ui_base", "theme": { "name": "theme-dark", @@ -61,8 +72,8 @@ "reset": false }, "darkTheme": { - "default": "#097479", - "baseColor": "#097479", + "default": "{{ darktheme-default }}", + "baseColor": "{{ darktheme-basecolor }}", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", "edited": true, "reset": false @@ -71,16 +82,17 @@ "name": "Untitled Theme 1", "default": "#4B7930", "baseColor": "#4B7930", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false }, "themeState": { "base-color": { - "default": "#097479", - "value": "#097479", + "default": "{{ base-color-default }}", + "value": "{{ base-color-value }}", "edited": false }, "page-titlebar-backgroundColor": { - "value": "#097479", + "value": "{{ page-titlebar-bgcolor }}", "edited": false }, "page-backgroundColor": { @@ -108,7 +120,7 @@ "edited": false }, "widget-backgroundColor": { - "value": "#097479", + "value": "{{ widget-bgcolor }}", "edited": false }, "widget-borderColor": { @@ -128,190 +140,123 @@ } }, "site": { - "name": "OpenScan 3D Scanner", + "name": "OpenScan", "hideToolbar": "false", "allowSwipe": "false", "lockMenu": "false", "allowTempTheme": "true", "dateFormat": "DD/MM/YYYY", "sizes": { - "sx": 46, - "sy": 46, - "gx": 10, - "gy": 10, + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, "cx": 6, "cy": 6, - "px": 6, - "py": 6 + "px": 0, + "py": 0 } } }, { - "id": "729f9ea6e3513c9b", - "type": "ui_group", - "name": "Home", - "tab": "b3150b13e34b1fe8", - "order": 2, - "disp": false, - "width": "6", - "collapse": false, - "className": "" + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 }, { - "id": "65ae49b64fa0d83e", - "type": "ui_tab", - "name": "Settings", - "icon": "dashboard", - "order": 4, - "disabled": false, - "hidden": false + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 }, { - "id": "4fe6b4c0ade0938a", + "id": "b5fdd57b.15eda8", "type": "ui_group", - "name": "General", - "tab": "65ae49b64fa0d83e", + "name": "Main", + "tab": "15a222ed.d70a7d", "order": 1, - "disp": true, - "width": "6", - "collapse": true, - "className": "" + "disp": false, + "width": 13, + "collapse": false }, { - "id": "0fe66c9190b8a87c", + "id": "db43d646.2074c8", "type": "ui_group", - "name": "Network", - "tab": "65ae49b64fa0d83e", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", "order": 2, "disp": true, "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "93aadb71dee6d977", - "type": "ui_group", - "name": "Camera", - "tab": "65ae49b64fa0d83e", - "order": 4, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "d49a6dfd7fb17096", - "type": "ui_group", - "name": "Motor", - "tab": "65ae49b64fa0d83e", - "order": 5, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "644b3bcc903d46ca", - "type": "ui_group", - "name": "Pinout", - "tab": "65ae49b64fa0d83e", - "order": 6, - "disp": true, - "width": "6", - "collapse": true, - "className": "" + "collapse": false }, { - "id": "e23b837a9f040895", + "id": "15a222ed.d70a7d", "type": "ui_tab", - "name": "Scan", + "name": "Files&Cloud", "icon": "dashboard", - "order": 2, + "order": 3, "disabled": false, "hidden": false }, { - "id": "7aaf184330605300", + "id": "365a30d0dfa83e95", "type": "ui_group", - "name": "Settings", + "name": "settings", "tab": "e23b837a9f040895", "order": 1, "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "ce9cc9d915dc6eb6", - "type": "ui_group", - "name": "Picamera", - "tab": "e23b837a9f040895", - "order": 2, - "disp": false, - "width": "12", + "width": 7, "collapse": false, "className": "" }, { - "id": "90223f7ddc082321", + "id": "ac7409105cfecac6", "type": "ui_group", - "name": "Arducam", + "name": "advanced", "tab": "e23b837a9f040895", "order": 3, "disp": false, - "width": 12, + "width": 7, "collapse": false, "className": "" }, { - "id": "7625f9c9e8dbc5c6", - "type": "ui_spacer", - "z": "017bd4e4a428bee5", - "name": "spacer", - "group": "", - "order": 4, - "width": 1, - "height": 1 - }, - { - "id": "3b4bd36726be16d5", + "id": "729f9ea6e3513c9b", "type": "ui_group", - "name": "OpenScanCloud", - "tab": "65ae49b64fa0d83e", - "order": 3, - "disp": true, + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, "width": "6", "collapse": false, "className": "" }, { - "id": "b5fdd57b.15eda8", + "id": "5b3e5aca21140e9a", "type": "ui_group", - "name": "Main", - "tab": "15a222ed.d70a7d", + "name": "Update", + "tab": "b3150b13e34b1fe8", "order": 1, "disp": false, - "width": 13, - "collapse": false - }, - { - "id": "db43d646.2074c8", - "type": "ui_group", - "name": "OpenScanCloud", - "tab": "15a222ed.d70a7d", - "order": 2, - "disp": true, "width": "6", - "collapse": false + "collapse": false, + "className": "" }, { - "id": "15a222ed.d70a7d", + "id": "b3150b13e34b1fe8", "type": "ui_tab", - "name": "Files&Cloud", + "name": "OpenScan", "icon": "dashboard", - "order": 3, + "order": 1, "disabled": false, - "hidden": false + "hidden": true }, { "id": "ddbd496e.93a288", @@ -339,285 +284,284 @@ "type": "ui_tab", "name": "Update & Info", "icon": "dashboard", - "order": 5, + "order": 4, "disabled": false, "hidden": false }, { - "id": "1f7f7e1e24f5ad9b", + "id": "4390b2ebcbbe104c", "type": "ui_group", - "name": "Initialize", - "tab": "b3150b13e34b1fe8", - "order": 3, - "disp": false, + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, "width": "6", - "collapse": false, + "collapse": true, "className": "" }, { - "id": "5b3e5aca21140e9a", + "id": "8ab79a98e536e0d6", "type": "ui_group", - "name": "Update", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, "width": "6", "collapse": false, "className": "" }, { - "id": "700f47327133ab68", + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", "type": "ui_spacer", - "z": "829d803b6033a693", + "z": "e43a27722b508115", "name": "spacer", - "group": "729f9ea6e3513c9b", - "order": 6, + "group": "db43d646.2074c8", + "order": 1, "width": 1, "height": 1 }, { - "id": "ebf828f29201a53b", + "id": "33b6d7317d1524b8", "type": "ui_spacer", - "z": "829d803b6033a693", + "z": "e43a27722b508115", "name": "spacer", - "group": "729f9ea6e3513c9b", - "order": 8, + "group": "db43d646.2074c8", + "order": 3, "width": 1, "height": 1 }, { - "id": "3b4961c4e72ff58a", + "id": "aaf5b874c52a58aa", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "4fe6b4c0ade0938a", - "order": 6, - "width": 6, + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, "height": 1 }, { - "id": "5ef40dca2c6c6aab", + "id": "f8d8740dcbf499fb", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "4fe6b4c0ade0938a", + "group": "365a30d0dfa83e95", "order": 11, - "width": 6, + "width": 1, "height": 1 }, { - "id": "bdd26746cc1e1ba0", + "id": "7ac0cb556740d159", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "3b4bd36726be16d5", - "order": 6, - "width": 2, + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, "height": 1 }, { - "id": "3584b5ef2b7acb72", + "id": "4de2414e29020c74", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "3b4bd36726be16d5", - "order": 8, - "width": 2, + "group": "90223f7ddc082321", + "order": 2, + "width": 7, "height": 1 }, { - "id": "cac67f0e.f01fa", - "type": "ui_group", - "name": "Button Top", - "tab": "", - "order": 1, - "disp": true, - "width": "6", - "collapse": false + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 }, { - "id": "b73c392ffd8ca3f2", + "id": "ce21673092264c38", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "90223f7ddc082321", - "order": 14, - "width": 2, + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, "height": 1 }, { - "id": "89fe04171cd2f35b", + "id": "3f7b77f8a1675d27", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "90223f7ddc082321", - "order": 15, - "width": 2, + "group": "12b719cba49817c9", + "order": 7, + "width": 4, "height": 1 }, { - "id": "80c9c0059de08f02", + "id": "0799b02d12fc3a14", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "90223f7ddc082321", - "order": 16, - "width": 2, + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, "height": 1 }, { - "id": "3fe52603e2ac73b6", - "type": "ui_template", - "z": "829d803b6033a693", - "group": "729f9ea6e3513c9b", - "name": "Background", - "order": 1, - "width": 0, - "height": 0, - "format": "", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": false, - "templateScope": "global", - "className": "", - "x": 110, - "y": 40, - "wires": [ - [] - ] + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" }, - { - "id": "4468f691.103eb8", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 2, - "width": 3, - "height": 2, - "passthru": false, - "label": "SCAN", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "1", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 100, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] +{ + "id": "15edc2ce885dddb3", + "type": "ui_group", + "name": "Colorines", + "tab": "457102eadc9ddb6c", + "order": 8, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, +{ + "id": "33aff36289823faa", + "type": "ui_group", + "name": "Monitoring", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, + "width": "6", + "collapse": false, + "className": "" }, { - "id": "6560dd25.9e76c4", - "type": "ui_button", - "z": "829d803b6033a693", + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", "name": "", - "group": "729f9ea6e3513c9b", - "order": 4, - "width": 3, - "height": 2, - "passthru": false, - "label": "Settings", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "3", - "payloadType": "num", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, "topic": "", - "topicType": "str", + "payload": "", + "payloadType": "date", "x": 100, - "y": 180, + "y": 460, "wires": [ [ - "62cd5288.2805fc" + "949bafced17d66d6" ] ] }, { - "id": "62cd5288.2805fc", - "type": "ui_ui_control", - "z": "829d803b6033a693", - "name": "", - "events": "all", - "x": 280, - "y": 100, + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, "wires": [ [] ] }, { - "id": "71e72293.91c6fc", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 3, - "width": 3, - "height": 2, - "passthru": false, - "label": "Files", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "2", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 140, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "e7306ef2.3b4df", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 5, - "width": 3, - "height": 2, - "passthru": false, - "label": "Update&Info", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "4", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 110, - "y": 220, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "88edad7ca53698fd", + "id": "a1f0ed7d5a9d670e", "type": "inject", - "z": "829d803b6033a693", - "name": "1s", + "z": "e6f4d02efb300ea9", + "name": "", "props": [ { - "p": "payload" + "p": "overwrite", + "v": "false", + "vt": "bool" }, { "p": "topic", @@ -627,112 +571,60 @@ "repeat": "", "crontab": "", "once": true, - "onceDelay": "1", + "onceDelay": "0.1", "topic": "", - "payload": "true", - "payloadType": "bool", - "x": 90, - "y": 400, + "x": 110, + "y": 60, "wires": [ [ - "000a811a215e08d4", - "83c2b5ea51f0fec3", - "88fde4ab78c965d7", - "bee62d2a99cbc63b", - "8e39e4a037487ecd", - "bb84b9e5c7d8e21f", - "7113d7b25a851151", - "c4c1580c289fc7bd" + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" ] ] }, { - "id": "bd75f33b8a57c522", - "type": "link out", - "z": "829d803b6033a693", - "name": "enable", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "92c98e6ce7cd25f9", - "b33d604c.5f1a6" - ], - "x": 335, - "y": 440, - "wires": [] - }, - { - "id": "000a811a215e08d4", + "id": "544d20f02215011a", "type": "function", - "z": "829d803b6033a693", - "name": "enable", - "func": "msg.enabled = true\nmsg.payload = 1\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 230, - "y": 440, + "x": 330, + "y": 60, "wires": [ [ - "bd75f33b8a57c522" + "c77552216a8bb781" ] ] }, { - "id": "83c2b5ea51f0fec3", - "type": "function", - "z": "829d803b6033a693", - "name": "disable", - "func": "msg.enabled = false\nreturn msg", + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 240, - "y": 480, + "x": 540, + "y": 60, "wires": [ [ - "6b94bf2295b1b31d" + "960912e90ba5b5bc" ] ] }, - { - "id": "6b94bf2295b1b31d", - "type": "link out", - "z": "829d803b6033a693", - "name": "disable", - "mode": "link", - "links": [ - "a1d29e56599da0bd" - ], - "x": 335, - "y": 480, - "wires": [] - }, - { - "id": "88fde4ab78c965d7", - "type": "function", - "z": "829d803b6033a693", - "name": "write", - "func": "var file = 'status_cloud'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\ncontent = 'ready'\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 520, - "wires": [ - [] - ] - }, { "id": "960912e90ba5b5bc", "type": "link out", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "started1s", "mode": "link", "links": [ @@ -752,16 +644,43 @@ "e5f38b4a07a5e278", "f0b355967b33dfee", "d0104e0163745993", - "5e7d5e4335d37794" + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" ], - "x": 615, - "y": 800, + "x": 645, + "y": 60, "wires": [] }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, { "id": "168d72a54504b327", "type": "inject", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "5/0.1s", "props": [ { @@ -780,7 +699,7 @@ "payload": "", "payloadType": "str", "x": 100, - "y": 720, + "y": 380, "wires": [ [ "6c6ef2255a7d39e5" @@ -790,64 +709,106 @@ { "id": "6c6ef2255a7d39e5", "type": "link out", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "repeat 5s/0.1s", "mode": "link", "links": [ "61990987acd0f263", - "2415272f42ce468c" + "2415272f42ce468c", + "6bf8344af427a6ba" ], - "x": 195, - "y": 720, + "x": 205, + "y": 380, "wires": [] }, { - "id": "bee62d2a99cbc63b", - "type": "function", - "z": "829d803b6033a693", - "name": "global", - "func": "global.set('flag_pw', true)\nglobal.set('flag', true)\nglobal.set('combine', false)\nglobal.set('focus', 2838)\nglobal.set('focus1', 0)\nglobal.set('focus2', 0)\n\nglobal.set('focuser', true)\n", + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 400, + "x": 270, + "y": 140, "wires": [ [ - "f20da2fc4978b7bf" + "eb1a2387a1eeea76" ] ] }, { - "id": "544d20f02215011a", + "id": "b1e2491c952f84c9", "type": "function", - "z": "829d803b6033a693", - "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cameras':{\n 'imx519':[4656,3496],\n 'imx219':[3280,2464],\n 'imx477':[4056,3040],\n 'ov5647':[2592,1944],\n 'imx378':[3840,2880],\n 'ov9271':[1280,800],\n 'imx290a':[1920,1080],\n 'imx290b':[1920,1080],\n },\n 'cam_AFmode':true,\n 'cam_STmode':true,\n 'cam_stacksize':2,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':0,\n 'cam_saturation':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'hostname':'',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n 'pin_endstop1':24,\n 'pin_endstop2':25,\n 'pin_external':10,\n 'pin_ringlight1':17,\n 'pin_ringlight2':27,\n 'pin_rotor_dir':5,\n 'pin_rotor_enable':23,\n 'pin_rotor_step':6,\n 'pin_tt_dir':9,\n 'pin_tt_enable':22,\n 'pin_tt_step':11,\n 'rotor_acc':1,\n 'rotor_accramp':2000,\n 'rotor_angle':10,\n 'rotor_anglemax':60,\n 'rotor_anglemin':-20,\n 'rotor_anglestart':0,\n 'rotor_delay':0.0001,\n 'rotor_dir':1,\n 'rotor_stepsperrotation':48000,\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n 'tt_acc':1,\n 'tt_accramp':200,\n 'tt_angle':10,\n 'tt_delay':0.0001,\n 'tt_dir':1,\n 'tt_stepsperrotation':3200,\n 'cam_focus':2838,\n 'cam_focus1':0,\n 'cam_focus2':0,\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'downscale_threshold':1000,\n 'turntable_mode':false,\n 'timeout_ringlight':300,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 310, - "y": 800, + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, "wires": [ [ - "c77552216a8bb781" + "200d4b9951b6e066" ] ] }, { - "id": "a1f0ed7d5a9d670e", + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", "type": "inject", - "z": "829d803b6033a693", - "name": "", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", "props": [ { "p": "overwrite", - "v": "false", + "v": "true", "vt": "bool" }, { @@ -857,11 +818,11 @@ ], "repeat": "", "crontab": "", - "once": true, + "once": false, "onceDelay": "0.1", "topic": "", - "x": 90, - "y": 800, + "x": 800, + "y": 40, "wires": [ [ "544d20f02215011a" @@ -869,89 +830,181 @@ ] }, { - "id": "c77552216a8bb781", + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", "type": "python3-function", - "z": "829d803b6033a693", - "name": "chk files", - "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", "outputs": 1, - "x": 520, - "y": 800, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, "wires": [ [ - "960912e90ba5b5bc", - "ea0e57d83f291e23" + "62cd5288.2805fc" ] ] }, { - "id": "38783aea9cc317a6", - "type": "link in", - "z": "829d803b6033a693", - "name": "factory reset", - "links": [ - "80bccc884b0be297", - "beacc3dc5398fa79" - ], - "x": 135, - "y": 840, + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, "wires": [ [ - "544d20f02215011a" + "62cd5288.2805fc" ] ] }, { - "id": "f20da2fc4978b7bf", - "type": "link out", - "z": "829d803b6033a693", - "name": "global", - "mode": "link", - "links": [ - "d14bbbb446d45e39" - ], - "x": 345, - "y": 400, - "wires": [] + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] }, { - "id": "8e39e4a037487ecd", - "type": "python3-function", - "z": "829d803b6033a693", - "name": "create log", - "func": "import subprocess\nfrom time import sleep\nsleep(20)\n\n\nlog = '############################################DMESG############################################\\n'\nlog += subprocess.getoutput(\"dmesg\")\nlog += '\\n############################################SYSLOG############################################\\n'\nlog += subprocess.getoutput(\"tail -10000 /var/log/syslog\")\n\nwith open('/home/pi/OpenScan/tmp/log.txt', 'w+') as file:\n file.write(log)\n\nreturn msg", - "outputs": 1, - "x": 240, - "y": 560, + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, "wires": [ - [] + [ + "62cd5288.2805fc" + ] ] }, { - "id": "be8cae9cf6f3585f", - "type": "ui_template", - "z": "829d803b6033a693", - "group": "1f7f7e1e24f5ad9b", - "name": "first start", - "order": 1, - "width": 6, - "height": 3, - "format": "

Initial Setup

\n

Note, that you can always adjust these and other settings in the settings menu, which will appear after this setup stage. 

\n

Model

\n

Please select the OpenScan Version - this will only affect the motor settings (acceleration, gear ratio, speed).

\n

Camera

\n

- Pi Camera v1, v2, HQ, Arducam IMX519, IMX290, IMX378, OV9281 are connected through the ribbon cable. If you encounter any issues, please check the cable's orientation

\n

- DSLR (gphoto) - can be used with a wide range of cameras, which can be connected and controlled via USB. Check GPhoto if your camera is supported

\n

- External Camera - Can be used to connect your camera trigger to the GPIO pins on the front of the pi shield. This can be used with any (modified) remote shutter release, and thus it is possible to use Smartphones, DSLR and compact cameras

", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", "className": "", - "x": 280, - "y": 40, + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, "wires": [ - [] + [ + "62cd5288.2805fc" + ] ] }, { "id": "8955d11554f55e63", "type": "ui_button", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "", "group": "5b3e5aca21140e9a", "order": 1, @@ -969,7 +1022,7 @@ "topic": "", "topicType": "str", "x": 120, - "y": 280, + "y": 720, "wires": [ [ "1e7457ea9c2c5e09" @@ -979,145 +1032,44 @@ { "id": "1e7457ea9c2c5e09", "type": "link out", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "update", "mode": "link", "links": [ "39a502b38837273d" ], "x": 245, - "y": 280, - "wires": [] - }, - { - "id": "bb84b9e5c7d8e21f", - "type": "python3-function", - "z": "829d803b6033a693", - "name": "rescue incomplete project", - "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", - "outputs": 1, - "x": 290, - "y": 600, - "wires": [ - [] - ] - }, - { - "id": "a291fc98e4269c1b", - "type": "ui_text", - "z": "829d803b6033a693", - "group": "729f9ea6e3513c9b", - "order": 7, - "width": 4, - "height": 1, - "name": "version", - "label": "Version:", - "format": "{{msg.firmware}}", - "layout": "row-center", - "className": "", - "x": 460, - "y": 360, + "y": 720, "wires": [] }, { - "id": "7113d7b25a851151", + "id": "245e4341d4fb611c", "type": "function", - "z": "829d803b6033a693", - "name": "FIRMWARE VERSION", - "func": "msg.firmware = '2022-08-16'\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 280, - "y": 360, + "x": 790, + "y": 540, "wires": [ [ - "a291fc98e4269c1b", - "ec5cefa70ff535f7" - ] - ] - }, - { - "id": "ec5cefa70ff535f7", - "type": "ui_text", - "z": "829d803b6033a693", - "group": "ddbd496e.93a288", - "order": 2, - "width": 6, - "height": 1, - "name": "current version", - "label": "Current version:", - "format": "{{msg.firmware}}", - "layout": "row-spread", - "className": "", - "x": 480, - "y": 320, - "wires": [] - }, - { - "id": "c4c1580c289fc7bd", - "type": "python3-function", - "z": "829d803b6033a693", - "name": "create path", - "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", - "outputs": 1, - "x": 250, - "y": 640, - "wires": [ - [] - ] - }, - { - "id": "06d33bb8951ce668", - "type": "ui_template", - "z": "829d803b6033a693", - "group": "", - "name": "donate", - "order": 2, - "width": "0", - "height": "0", - "format": "\n\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "global", - "className": "", - "x": 450, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "245e4341d4fb611c", - "type": "function", - "z": "829d803b6033a693", - "name": "pinmap_v2", - "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 870, - "y": 40, - "wires": [ - [ - "627406f3611511dc" + "627406f3611511dc" ] ] }, { "id": "627406f3611511dc", "type": "python3-function", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "write", "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", "outputs": 1, - "x": 1010, - "y": 40, + "x": 930, + "y": 540, "wires": [ [ "50eeb3e362f9027f" @@ -1127,7 +1079,7 @@ { "id": "88b1bddde110298a", "type": "inject", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "", "props": [ { @@ -1145,8 +1097,8 @@ "once": false, "onceDelay": "0.1", "topic": "", - "x": 730, - "y": 40, + "x": 650, + "y": 540, "wires": [ [ "245e4341d4fb611c" @@ -1156,7 +1108,7 @@ { "id": "50eeb3e362f9027f", "type": "link out", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "started1s", "mode": "link", "links": [ @@ -1176,37 +1128,32 @@ "e5f38b4a07a5e278", "f0b355967b33dfee", "d0104e0163745993", - "5e7d5e4335d37794" + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" ], - "x": 1095, - "y": 40, - "wires": [] - }, - { - "id": "ea0e57d83f291e23", - "type": "debug", - "z": "829d803b6033a693", - "name": "", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 610, - "y": 860, + "x": 1015, + "y": 540, "wires": [] }, { "id": "4f3121f158f06a61", "type": "python3-function", - "z": "829d803b6033a693", - "name": "Rotor left", - "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',100,True)\n\nmotorrun('tt',360,True)\nmotorrun('extra',360,True)", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", "outputs": 1, - "x": 940, - "y": 80, + "x": 860, + "y": 580, "wires": [ [] ] @@ -1214,7 +1161,7 @@ { "id": "4a8a04b1e5dca8fe", "type": "inject", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "run rotor till endstop", "props": [ { @@ -1232,8 +1179,8 @@ "topic": "", "payload": "", "payloadType": "date", - "x": 770, - "y": 80, + "x": 690, + "y": 580, "wires": [ [ "4f3121f158f06a61" @@ -1241,935 +1188,323 @@ ] }, { - "id": "828e5298.d2192", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 9, - "width": 2, - "height": 1, - "passthru": false, - "label": "⇐", - "tooltip": "", - "color": "", - "bgcolor": "", + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 400, + "x": 580, + "y": 260, "wires": [ - [ - "b12e54fb.3141b8" - ] + [] ] }, { - "id": "96c7e241.458e6", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 10, - "width": 2, - "height": 1, - "passthru": false, - "label": "⇒", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 440, + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, "wires": [ [ - "37f52dd4.bd7572" + "cb6ebdabaaf7d0da" ] ] }, { - "id": "2e854876.6b6008", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 6, - "width": 2, - "height": 1, - "passthru": true, - "label": "⇑", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 280, + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, "wires": [ - [ - "555aea34.b3b5e4" - ] + [] ] }, { - "id": "753817f.1b9b3e8", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 7, - "width": 2, - "height": 1, - "passthru": true, - "label": "⇓", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 320, + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, "wires": [ [ - "9905e0c9.dddcd" + "c8a3fde5206ce1ae" ] ] }, { - "id": "8775044.3aa3ef8", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 8, - "width": 2, - "height": 1, - "name": "", - "label": "Turntable", - "format": "", - "layout": "row-left", - "className": "", - "x": 100, - "y": 360, - "wires": [] - }, - { - "id": "9e8a2d23.bf6ce", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 5, - "width": 2, - "height": 1, - "name": "", - "label": "Rotor", - "format": "", - "layout": "row-left", - "className": "", - "x": 90, + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, "y": 240, - "wires": [] + "wires": [ + [ + "87be854db758a9a6" + ] + ] }, { - "id": "555aea34.b3b5e4", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, - "x": 220, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, "y": 280, "wires": [ [ - "46e00b45.c24ca4" + "9daea4bd57f7a00e" ] ] }, { - "id": "9905e0c9.dddcd", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 320, - "wires": [ - [ - "6ee089cb343a35ef" - ] - ] - }, - { - "id": "b12e54fb.3141b8", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 400, - "wires": [ - [ - "c1871a2b9af5419a" - ] - ] - }, - { - "id": "37f52dd4.bd7572", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 440, - "wires": [ - [ - "42b9f1fc49e69f54" - ] - ] - }, - { - "id": "46e00b45.c24ca4", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Rotor left", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('rotor',load_int('rotor_angle'))", - "outputs": 1, - "x": 360, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "6ee089cb343a35ef", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Rotor right", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('rotor',-load_int('rotor_angle'))", - "outputs": 1, - "x": 370, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "42b9f1fc49e69f54", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "TT right", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('tt',-load_int('tt_angle'))", + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, - "x": 360, - "y": 440, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, "wires": [ [] ] }, { - "id": "c1871a2b9af5419a", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "TT left", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('tt',load_int('tt_angle'))", + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, - "x": 350, - "y": 400, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, "wires": [ [] ] }, { - "id": "aebad788761dce4a", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "routine_photocount", + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", "label": "", "tooltip": "", - "group": "7aaf184330605300", - "order": 14, - "width": 3, + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, "height": 1, - "passthru": false, - "outs": "end", + "passthru": true, + "mode": "text", + "delay": "0", "topic": "", - "topicType": "str", - "min": "10", - "max": "300", - "step": "10", + "sendOnBlur": true, "className": "", - "x": 350, - "y": 540, + "topicType": "str", + "x": 320, + "y": 80, "wires": [ [ - "ce28a0b5bfb0d5a1" + "734ac3bff2df6837" ] ] }, { - "id": "107a030938cbfea9", + "id": "97170908e1f4ac55", "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 190, - "y": 540, + "y": 80, "wires": [ [ - "aebad788761dce4a" + "58e565fea35cb667" ] ] }, { - "id": "ce28a0b5bfb0d5a1", + "id": "734ac3bff2df6837", "type": "function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "write", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 510, - "y": 540, + "y": 80, "wires": [ [] ] }, { - "id": "84d6b96c8ebaac96", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadF", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 580, - "wires": [ - [ - "470b10726d298834" - ] - ] - }, - { - "id": "470b10726d298834", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "shutter ", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 16, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "700", - "step": "1", - "className": "", - "x": 310, - "y": 580, + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, "wires": [ [ - "44c3947a9b92d32d" + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" ] ] }, { - "id": "44c3947a9b92d32d", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], "x": 510, - "y": 580, + "y": 200, "wires": [ [] ] }, { - "id": "069bcf58b1fe44cd", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 13, - "width": 3, - "height": 1, - "name": "photocount", - "label": "Photos", - "format": "", - "layout": "row-left", - "className": "", - "x": 670, - "y": 540, - "wires": [] - }, - { - "id": "8dc7df1de59cb03a", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 15, - "width": 3, - "height": 1, - "name": "shutter", - "label": "Shutter (ms)", - "format": "", - "layout": "row-left", - "className": "", - "x": 650, - "y": 580, - "wires": [] - }, - { - "id": "cc69dba8d54a29dd", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "Crop X", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 18, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", "topic": "", - "topicType": "str", - "min": "0", - "max": "99", - "step": "1", - "className": "", - "x": 320, - "y": 620, + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, "wires": [ [ - "c2b2ab5524271123" + "11f41a6030578ef4" ] ] }, { - "id": "e3a90602605fb9e9", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "Crop Y", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 20, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "99", - "step": "1", - "className": "", + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], "x": 310, - "y": 660, + "y": 200, "wires": [ [ - "26f17a7f406df73c" + "a0156eaac7dd35e5" ] ] }, { - "id": "9c6b48b7b4cc4e1a", + "id": "855cbcadef1163c5", "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 190, - "y": 620, + "x": 250, + "y": 840, "wires": [ [ - "cc69dba8d54a29dd" + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" ] ] }, { - "id": "c470fd0b15356206", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 660, - "wires": [ - [ - "e3a90602605fb9e9" - ] - ] - }, - { - "id": "c2b2ab5524271123", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 620, - "wires": [ - [] - ] - }, - { - "id": "26f17a7f406df73c", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 660, - "wires": [ - [] - ] - }, - { - "id": "fecf5cff888bb570", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 17, - "width": 3, - "height": 1, - "name": "cropx", - "label": "{{msg.crop1}}", - "format": "", - "layout": "row-left", - "className": "", - "x": 690, - "y": 620, - "wires": [] - }, - { - "id": "0ee4950bd21498bd", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 19, - "width": 3, - "height": 1, - "name": "cropy", - "label": "{{msg.crop2}}", - "format": "", - "layout": "row-left", - "className": "", - "x": 690, - "y": 660, - "wires": [] - }, - { - "id": "ebbf11b55d758806", - "type": "ui_text_input", - "z": "1613373abaf77a2c", - "name": "", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 4, - "width": 3, - "height": 1, - "passthru": true, - "mode": "text", - "delay": "0", - "topic": "", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 320, - "y": 500, - "wires": [ - [ - "67385b196c517ac6" - ] - ] - }, - { - "id": "f4b3112a9ec6c487", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.payload=\"default\"\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 500, - "wires": [ - [ - "ebbf11b55d758806" - ] - ] - }, - { - "id": "67385b196c517ac6", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 500, - "wires": [ - [] - ] - }, - { - "id": "4dd7285c2b0fd79b", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "ringlight", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 12, - "width": 3, - "height": 1, - "passthru": true, - "outs": "all", - "topic": "", - "topicType": "str", - "min": 0, - "max": "3", - "step": 1, - "className": "", - "x": 320, - "y": 700, - "wires": [ - [ - "873dace18a23fdf2" - ] - ] - }, - { - "id": "873dace18a23fdf2", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "LED", - "func": "from OpenScan import ringlight\nval = msg['payload']\n\nif val == 0:\n ringlight(1,False)\n ringlight(2,False)\nelif val == 1:\n ringlight(1,False)\n ringlight(2,True)\nelif val == 2:\n ringlight(1,True)\n ringlight(2,False)\nelif val == 3:\n ringlight(1,True)\n ringlight(2,True)", - "outputs": 1, - "x": 510, - "y": 700, - "wires": [ - [] - ] - }, - { - "id": "9e30e33a1520fee0", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "msg.payload = 0\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 700, - "wires": [ - [ - "4dd7285c2b0fd79b" - ] - ] - }, - { - "id": "7dd287f40385922f", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "start ", - "group": "7aaf184330605300", - "order": 21, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-play", - "payload": "", - "payloadType": "date", - "topic": "enabled", - "topicType": "str", - "x": 150, - "y": 880, - "wires": [ - [ - "431f917c2541ae48", - "33d94a04b96a2de0", - "6d15f717d5a11002" - ] - ] - }, - { - "id": "579f2211199fd6ab", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "stop", - "group": "7aaf184330605300", - "order": 23, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-stop", - "payload": "numberofphotos", - "payloadType": "global", - "topic": "", - "topicType": "str", - "x": 810, - "y": 960, - "wires": [ - [ - "1787f08ed7070ddd", - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "431f917c2541ae48", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, create_coordinates, take_photo, save, load_bool, camera\nfrom time import sleep, strftime, time\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system\nfrom os.path import isfile\nfrom Arducam import Focuser\n\nif load_str(\"status_internal_cam\")==\"no camera found\" or load_str(\"status_internal_cam\")[:5]==\"Featu\":\n return\n\nsave('status_internal_cam','Routine-preparing')\n\nprojectname=load_str(\"routine_projectname\")\nphotocount = load_int('routine_photocount') #vorher point_count\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nangle_start = load_int('rotor_anglestart')\ncam = load_str('camera')\nSTmode = load_bool('cam_STmode')\ntt_mode = load_bool('turntable_mode')\ncam_delay_after = load_float('cam_delay_after')\ncam_delay_before = load_float('cam_delay_before')\n\nif cam == 'imx519' and STmode == True:\n focuser = Focuser('/dev/v4l-subdev1')\n stacksize = load_int('cam_stacksize')\n focus1 = load_int('cam_focus1')\n focus2 = load_int('cam_focus2')\n if focus1 > focus2:\n focus2 = focus1\n focus1 = load_int('cam_focus2') \n focusstep = int((focus2-focus1)/(stacksize - 1))\n\ncounter = 0\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp/tmp.jpg'\nzippath = basepath + 'tmp/tmp.zip'\n\nif not 'projectcode' in msg:\n projectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n msg['projectcode'] = projectcode\n msg['counter'] = -1\n if isfile(zippath):\n system('rm ' + zippath)\n sleep(1)\n\nprojectcode = msg['projectcode']\nmsg['counter'] += 1\n\nif tt_mode == False:\n coordinates = create_coordinates(angle_min,angle_max,photocount)\nelse:\n angle_start = 0\n coordinates = []\n for i in range (photocount):\n coordinates.append([0,360/photocount*(i+1)])\n\nposition_last = (angle_start , 0)\n\nzip = ZipFile(zippath, \"a\",ZIP_DEFLATED, allowZip64=True)\n\nstarttime = time()\n\nfor position in coordinates:\n counter += 1\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n\n while load_str('status_internal_cam') == 'Routine-paused':\n sleep(0.2)\n\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n\n rotor_angle = position_last[0] - position[0]\n if abs(rotor_angle) > 180:\n rotor_angle = -360 * rotor_angle/abs(rotor_angle) + rotor_angle\n\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n # tt_angle = -360 * tt_angle/abs(tt_angle) + tt_angle\n \n motorrun('rotor', rotor_angle)\n motorrun('tt', tt_angle)\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n while load_str('status_internal_cam') == 'Routine-paused':\n sleep(0.2)\n\n msg['cropx'] = load_int('cam_cropx')\n msg['cropy'] = load_int('cam_cropy')\n msg['rotation'] = load_int('cam_rotation')\n msg['filepath_in'] = 'tmp/tmp.jpg'\n msg['filepath_out'] = 'tmp/tmp.jpg'\n msg['filepath'] = 'tmp/tmp.jpg'\n\n if counter < 6:\n ETA = ''\n sleep(cam_delay_before)\n if STmode == True:\n counter2 = 0\n for focus in range (stacksize):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n while load_str('status_internal_cam') == 'Routine-paused':\n sleep(0.2)\n counter2 += 1\n save('status_internal_cam','Routine-' + str(counter) + '/' + str(photocount) + ' F' + str(counter2) + ETA)\n focuser.write(focus1 + focus * focusstep)\n take_photo('tmp/tmp.jpg')\n camera('/crop',msg)\n zip.write(temppath, projectname + '_' + str(msg['counter']) + '_' + str(counter) + '-' + str(focus) + \".jpg\")\n system('cp ' + temppath + ' ' + basepath +'tmp/preview.jpg')\n elif cam != 'external':\n save('status_internal_cam','Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n\n if cam == 'gphoto':\n camera('/gphoto_capture', msg)\n if cam in ('usb_webcam','imx219','ov5647','imx477','imx290a','imx290b','imx378','ov9281','imx519'):\n take_photo('tmp/tmp.jpg')\n camera('/crop',msg)\n \n zip.write(temppath, projectname + '_' + str(msg['counter']) + '_' + str(counter) + \".jpg\")\n system('cp ' + temppath + ' ' + basepath +'tmp/preview.jpg')\n elif cam == 'external':\n camera('external_capture')\n save('status_internal_cam','Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n\n ETA = '-ETA:'+str(int((photocount/counter - 1)*(time() - starttime)))+'/'+str(int(photocount/counter*(time() - starttime)))+'s'\n sleep(cam_delay_after)\n\n position_last = position\n\nzip.close()\n\nsave('status_internal_cam','Routine-done')\n\nmotorrun('rotor',position_last[0] - angle_start)\nmotorrun('tt',position_last[1])\n\nsave('status_internal_cam','--READY--')\n\nif load_bool('routine_secondpass')==True:\n msg['topic'] = 'Scan done'\n msg['payload'] = 'Do you want to run another pass or finish this project?'\n msg['enabled'] = False\n return msg,None\n\nreturn None,msg\n", - "outputs": 2, - "x": 300, - "y": 880, - "wires": [ - [ - "db7eea74d3bf892b" - ], - [ - "0b8661103366f834" - ] - ] - }, - { - "id": "1787f08ed7070ddd", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "stop", - "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", - "outputs": 1, - "x": 930, - "y": 960, - "wires": [ - [] - ] - }, - { - "id": "e9b13dfd9f8d3711", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6" - ], - "x": 395, - "y": 840, - "wires": [] - }, - { - "id": "9654deebb668e012", - "type": "inject", - "z": "1613373abaf77a2c", - "name": "1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "1", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 290, - "y": 1000, - "wires": [ - [ - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "8367cfa0bf5bc5df", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start routine", - "links": [ - "210ef5246d1a8790", - "84608db962fd9932", - "8689e938.dd9e38", - "f20f2dbc.0f123", - "e9b13dfd9f8d3711", - "96bdb9417e38810f", - "fb13752beddee9f2", - "bd75f33b8a57c522" - ], - "x": 55, - "y": 880, - "wires": [ - [ - "7dd287f40385922f" - ] - ] - }, - { - "id": "fb13752beddee9f2", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "8367cfa0bf5bc5df", - "b33d604c.5f1a6" - ], - "x": 895, - "y": 920, - "wires": [] - }, - { - "id": "95439678bb2df2a2", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "msg.flag = global.get('flag')\nif (global.get('flag_pw')== true){\n return msg\n}\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 1220, - "wires": [ - [ - "04cc2467807d2d6b", - "14f9617b5b301318" - ] - ] - }, - { - "id": "948a3ae4444685f2", + "id": "1a443e20a973d2f1", "type": "change", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "flag_pw true", "rules": [ { @@ -2185,16 +1520,16 @@ "from": "", "to": "", "reg": false, - "x": 610, - "y": 1260, + "x": 630, + "y": 760, "wires": [ [] ] }, { - "id": "04cc2467807d2d6b", + "id": "d1b87196ae5373ed", "type": "change", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "flag_pw false", "rules": [ { @@ -2210,79 +1545,16 @@ "from": "", "to": "", "reg": false, - "x": 390, - "y": 1260, + "x": 430, + "y": 760, "wires": [ [] ] }, { - "id": "12f1399b240830bf", - "type": "exec", - "z": "1613373abaf77a2c", - "command": " v4l2-ctl --list-formats-ext", - "addpay": "", - "append": "", - "useSpawn": "true", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "check cam", - "x": 190, - "y": 100, - "wires": [ - [ - "6222f781629c72e7" - ], - [ - "6222f781629c72e7" - ], - [] - ] - }, - { - "id": "6222f781629c72e7", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\ncontent = '--READY--'\n\nif (msg.payload.includes('Cannot open device')){\n content = 'no camera found'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return msg\n }\n });\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 350, - "y": 100, - "wires": [ - [ - "e89c16809f8a5f1c" - ] - ] - }, - { - "id": "e978bf8c53d1f15a", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "Settings internal cam", - "info": "", - "x": 120, - "y": 40, - "wires": [] - }, - { - "id": "ccb7da246de908d1", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "preview internal cam", - "info": "", - "x": 110, - "y": 1160, - "wires": [] - }, - { - "id": "e9566588c5e40637", + "id": "03d92601c62b79d4", "type": "inject", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "4s/0.5", "props": [ { @@ -2293,1274 +1565,1127 @@ "vt": "str" } ], - "repeat": "0.5", + "repeat": "0.1", "crontab": "", "once": true, "onceDelay": "4", "topic": "Repeat", - "payload": "0.2", + "payload": "0.1", "payloadType": "str", - "x": 80, - "y": 1220, + "x": 100, + "y": 840, "wires": [ [ - "95439678bb2df2a2" + "855cbcadef1163c5" ] ] }, { - "id": "14f9617b5b301318", + "id": "41e6a4649b6afbfb", "type": "python3-function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nsleep(0.5)\n\n\nstatus = load_str('status_internal_cam')\ncam=load_str('camera')\n\n\nif msg['flag'] == False and not 'Routine' in status:\n return msg\n\nif cam == 'external':\n return\n\nmsg['payload']=\"/tmp/preview.jpg?ts=\"+str(int(time()*10))\n\nif cam == 'gphoto' and status == 'no camera found':\n if camera('/gphoto_init') == 200:\n save('status_internal_cam','--READY--')\n\nif status!=\"--READY--\":\n return msg\n\nmsg['cropx'] = load_int('cam_cropx')\nmsg['cropy'] = load_int('cam_cropy')\nmsg['rotation'] = load_int('cam_rotation')\nmsg['filepath_in'] = 'tmp/tmp.jpg'\nmsg['filepath_out'] = 'tmp/preview.jpg'\nmsg['filepath'] = 'tmp/tmp.jpg'\nmsg['preview'] = True\n\nif cam == 'gphoto':\n if camera('/gphoto_test', msg) != 200:\n save('status_internal_cam','no camera found')\n return msg\n camera('/gphoto_preview', msg)\n\nif cam in ('usb_webcam', 'imx219','ov5647','imx477','imx290a','imx290b','imx378','ov9281','imx519'):\n take_photo('tmp/tmp.jpg')\n\ncamera('/crop',msg)\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", "outputs": 1, - "x": 410, - "y": 1220, + "x": 450, + "y": 800, "wires": [ [ - "948a3ae4444685f2", - "991b587d406d0d91", - "8f5d87ce24c40b11" + "1a443e20a973d2f1", + "296636b7467fc745" ] ] }, { - "id": "991b587d406d0d91", + "id": "85a268108250ba88", "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "ce9cc9d915dc6eb6", - "name": "preview_internal", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", "order": 1, - "width": 12, - "height": 12, - "format": "
\n\n\n
\n", + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", "storeOutMessages": false, "fwdInMessages": false, "resendOnRefresh": false, "templateScope": "local", "className": "", - "x": 620, - "y": 1220, - "wires": [ - [] - ] - }, - { - "id": "1118d0965ff7c40b", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 3, - "width": 3, - "height": 1, - "name": "projectname", - "label": "Projectname", - "format": "", - "layout": "row-left", - "className": "", - "x": 670, - "y": 500, - "wires": [] - }, - { - "id": "82c8ad50ecfbc755", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 11, - "width": 3, - "height": 1, - "name": "ringlight", - "label": "Ringlight", - "format": "", - "layout": "row-left", - "className": "", - "x": 660, - "y": 700, - "wires": [] - }, - { - "id": "33d94a04b96a2de0", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 960, - "wires": [ - [ - "579f2211199fd6ab", - "c433515042ba01b5" - ] - ] - }, - { - "id": "c1c044f3c2139f68", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 810, - "y": 1000, + "x": 450, + "y": 840, "wires": [ [ - "579f2211199fd6ab", - "c433515042ba01b5" + "417f653ca0dfdcfc", + "180476141c2a44ad" ] ] }, { - "id": "9a368472a72fbc48", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "preview arducam with focus", - "info": "", - "x": 140, - "y": 1360, + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, "wires": [] }, { - "id": "8f5d87ce24c40b11", - "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "preview_arducam", - "order": 2, - "width": 10, - "height": 12, - "format": "
\n\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 630, - "y": 1300, + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, "wires": [ - [] + [ + "e864254b18c23dd1" + ] ] }, { - "id": "282efe64332193c8", + "id": "e864254b18c23dd1", "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "focus", - "func": "from OpenScan import load_str\n\nif load_str('camera') != 'imx519':\n return\n\nfrom Arducam import Focuser\n\n\nif msg['focuser'] == True:\n focuser = Focuser('/dev/v4l-subdev1')\n focuser.write(msg['focus'])\n return msg", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", "outputs": 1, - "x": 1110, - "y": 1460, + "x": 780, + "y": 840, "wires": [ [] ] }, { - "id": "64b16ef47ab6d859", - "type": "ui_switch", - "z": "1613373abaf77a2c", - "name": "MF", - "label": "", - "tooltip": "", - "group": "90223f7ddc082321", - "order": 4, - "width": 1, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "false", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "true", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 150, - "y": 1400, - "wires": [ - [ - "f017f67a8d4a3750" - ] - ] - }, - { - "id": "f017f67a8d4a3750", + "id": "180476141c2a44ad", "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "let fs = global.get('fs');\nfilepath = '/home/pi/OpenScan/settings/';\n\nvar file = 'status_internal_cam'\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data != '--READY--'){\n return\n}\n\nfile = 'cam_AFmode'\ncontent = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});\n\nglobal.set('AF',msg.payload)\nmsg.enabled = false\nif (msg.payload == false){\n msg.enabled = true\n}\nif (msg.payload == true){\n file = 'cam_focus1'\n content = String(0)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n file = 'cam_focus2'\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n \n file = 'cam_stacksize'\n content = String(2)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n global.set('focus1', 0)\n global.set('focus2', 0)\n\n}\n\n\nmsg.focus = global.get('focus')\nmsg.payload = 'down'\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 1400, + "x": 630, + "y": 880, "wires": [ [ - "5c39bd09.702d84", - "74521cf72050b515", - "b70e8c24ee011258", - "a2ff9dfd858821bc", - "ef62086d10d830fd" + "8cbdbfecbd12ef83" ] ] }, { - "id": "65145c939b6647e2", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 55, - "y": 1400, + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, "wires": [ - [ - "64b16ef47ab6d859" - ] + [] ] }, { - "id": "5ea18678.975138", - "type": "trigger", - "z": "1613373abaf77a2c", - "name": "20ms", - "op1": "", - "op2": "0", - "op1type": "pay", - "op2type": "str", - "duration": "-20", - "extend": false, - "overrideDelay": false, - "units": "ms", - "reset": "", - "bytopic": "all", - "topic": "topic", + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", "outputs": 1, - "x": 730, - "y": 1440, + "x": 440, + "y": 720, "wires": [ [ - "fd93843e238cc9ce" + "923be3b2b25224b4" ] ] }, { - "id": "5c39bd09.702d84", + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "F+", - "order": 8, - "width": 1, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, "height": 1, - "format": " ", + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", "storeOutMessages": true, "fwdInMessages": true, - "resendOnRefresh": false, + "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 410, - "y": 1400, + "x": 310, + "y": 160, "wires": [ [ - "dcfb5cce.0431a" + "034ec9f59e50a361", + "a0156eaac7dd35e5" ] ] }, { - "id": "dcfb5cce.0431a", - "type": "switch", - "z": "1613373abaf77a2c", - "name": "", - "property": "payload", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "1", - "vt": "num" - }, - { - "t": "eq", - "v": "-1", - "vt": "num" - }, - { - "t": "eq", - "v": "up", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 3, - "x": 550, - "y": 1420, + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, "wires": [ [ - "5ea18678.975138", - "f4a41b1e7b221486" - ], - [ - "5ea18678.975138", - "f4a41b1e7b221486" - ], - [ - "8cdd0a6b.40bcd8" + "194f3590dd4f6e3d" ] ] }, { - "id": "8cdd0a6b.40bcd8", - "type": "change", - "z": "1613373abaf77a2c", - "name": "", - "rules": [ - { - "t": "set", - "p": "reset", - "pt": "msg", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 560, - "y": 1480, + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, "wires": [ [ - "5ea18678.975138", - "e9b3837b1ffb0360" + "2de69452e829d780" ] ] }, { - "id": "74521cf72050b515", + "id": "cb6ebdabaaf7d0da", "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "F-", - "order": 9, - "width": 1, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, "height": 1, - "format": " ", + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", "storeOutMessages": true, "fwdInMessages": true, - "resendOnRefresh": false, + "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 410, - "y": 1440, + "x": 320, + "y": 120, "wires": [ [ - "dcfb5cce.0431a" + "7ef6f1b5c67201fe" ] ] }, { - "id": "7219f62c9fdc6753", + "id": "82ecd3cd971cb7ea", "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 7, - "width": 2, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, "height": 1, - "name": "", - "label": "{{msg.payload}}", + "name": "projectname", + "label": "Projectname", "format": "", - "layout": "col-center", + "layout": "row-left", "className": "", - "x": 1130, - "y": 1420, + "x": 530, + "y": 40, "wires": [] }, { - "id": "b70e8c24ee011258", - "type": "function", - "z": "1613373abaf77a2c", - "name": "global", - "func": "if (msg.payload == 'down'){\n msg.enabled = false\n msg.payload = ' '\n msg.focuser = global.get('focuser')\n return msg\n}\n\n\nmsg.enabled = true\n\nsign = msg.payload\nfocus = global.get('focus')\nif (focus > 3000){\n focusstep = 5\n}\nelse if (focus <=3000 && focus > 2000){\n focusstep = 3\n}\nelse{\n focusstep = 2\n}\n\n\nfocus = focus + sign * focusstep\n\nsign = msg.payload\nif (focus > 4000){\n distance = 6\n focus = 4000\n}\nelse if (focus > 1200 && focus <= 4000){\n distance = 737086 * Math.pow(focus, -1.4096)\n}\nelse if (focus <= 1200){\n distance = 999\n if (focus <=0){\n focus = 0\n }\n}\n\n\nglobal.set('focus', focus)\nmsg.focus = focus\nmsg.distance = distance\ndistance = distance * 10\nmsg.focuser = global.get('focuser')\nmsg.payload = String(distance.toFixed(1)) + 'mm'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 970, - "y": 1440, + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, "wires": [ [ - "7219f62c9fdc6753", - "282efe64332193c8", - "704a9f89089d1f25" + "06e1e19835a9816e" ] ] }, { - "id": "f4a41b1e7b221486", - "type": "change", - "z": "1613373abaf77a2c", - "name": "focuser f", - "rules": [ - { - "t": "set", - "p": "focuser", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 740, - "y": 1400, + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, "wires": [ - [] + [ + "1fe18f3b0b52aabd" + ] ] }, { - "id": "e9b3837b1ffb0360", - "type": "change", - "z": "1613373abaf77a2c", - "name": "focuser t", - "rules": [ - { - "t": "set", - "p": "focuser", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 740, - "y": 1480, + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, "wires": [ [] ] }, { - "id": "fd93843e238cc9ce", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "10ms", - "pauseType": "delay", - "timeout": "20", - "timeoutUnits": "milliseconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, - "x": 850, - "y": 1440, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, "wires": [ [ - "b70e8c24ee011258" + "ed2974731fb8a84e" ] ] }, { - "id": "25c4138bddb77b6b", - "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "set", + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", "order": 10, "width": 2, "height": 1, - "format": "set", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", "className": "", - "x": 570, - "y": 1540, + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, "wires": [ [ - "95e1d239988b29e0" + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" ] ] }, { - "id": "95e1d239988b29e0", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "focus = global.get('focus')\nfocus1 = global.get('focus1')\nfocus2 = global.get('focus2')\nlet fs = global.get('fs');\nfilepath = '/home/pi/OpenScan/settings/';\n \nif (msg.payload == false){\n return msg\n}\n\nif (focus1 != 0 && focus2 != 0){\n global.set('focus1', 0)\n global.set('focus2', 0)\n file = 'cam_focus1'\n content = String(0)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n file = 'cam_focus2'\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n msg.distance1 = ' '\n msg.distance2 = ' '\n msg.enabled = false\n return msg\n}\n\nif (focus > 4000){\n distance = 6\n focus = 4000\n}\nelse if (focus > 1200 && focus <= 4000){\n distance = 737086 * Math.pow(focus, -1.4096)\n}\nelse if (focus <= 1200){\n distance = 999.9\n if (focus <=0){\n focus = 0\n }\n}\ndistance = distance * 10\n\nif (focus1 == 0){\n global.set('focus1', focus)\n file = 'cam_focus1'\n content = String(focus)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n global.set('distance1', distance)\n msg.distance1 = distance.toFixed(1)\n msg.distance2 = 'tbd'\n msg.enabled = false\n return msg\n}\nif (focus1 != 0 && focus2 ==0 && focus!= focus1){\n global.set('focus2', focus)\n file = 'cam_focus2'\n content = String(focus)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n global.set('distance2', distance)\n msg.distance1 = global.get('distance1').toFixed(1)\n msg.distance2 = distance.toFixed(1)\n msg.enabled = true\n return msg\n}\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 710, - "y": 1560, + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, "wires": [ [ - "7889245e91ddea4b", - "210ef5246d1a8790" + "1787f08ed7070ddd", + "c1c044f3c2139f68" ] ] }, { - "id": "7889245e91ddea4b", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 11, - "width": 2, - "height": 1, + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", "name": "", - "label": "{{msg.distance1}}", - "format": "{{msg.distance2}}", - "layout": "col-center", - "className": "", - "x": 830, - "y": 1600, + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, "wires": [] }, { - "id": "a1d29e56599da0bd", + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", "type": "link in", - "z": "1613373abaf77a2c", - "name": "focusnumber", + "z": "481edaf6db5a7a54", + "name": "start routine", "links": [ - "210ef5246d1a8790", - "2dd2503d7ab0214b", - "6b94bf2295b1b31d" + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" ], - "x": 175, - "y": 1760, + "x": 45, + "y": 1040, "wires": [ [ - "06504f47ee1744d7", - "5f8b90ef08a7d68c" + "7dd287f40385922f" ] ] }, { - "id": "210ef5246d1a8790", + "id": "fb13752beddee9f2", "type": "link out", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "", "mode": "link", "links": [ - "a1d29e56599da0bd", + "2f4c0f98.dee2", "8367cfa0bf5bc5df", - "149e2e46b9623a2d" + "b33d604c.5f1a6", + "c8b93b42c720b9cf" ], - "x": 835, - "y": 1560, + "x": 525, + "y": 1040, "wires": [] }, { - "id": "b6f37e23f2491639", - "type": "ui_switch", - "z": "1613373abaf77a2c", - "name": "Stack", - "label": "", - "tooltip": "", - "group": "90223f7ddc082321", - "order": 6, - "width": 1, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 290, - "y": 1600, - "wires": [ - [ - "2d66216fee29250c" - ] - ] - }, - { - "id": "a2ff9dfd858821bc", + "id": "33d94a04b96a2de0", "type": "function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "enable", - "func": "msg.payload = false\nif (msg.enabled == false){\n return msg\n}\n", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 410, - "y": 1560, + "x": 290, + "y": 1100, "wires": [ [ - "25c4138bddb77b6b", - "7889245e91ddea4b", - "4cfada2de1c5bb74", - "95e1d239988b29e0" + "579f2211199fd6ab" ] ] }, { - "id": "2d66216fee29250c", + "id": "c1c044f3c2139f68", "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "file = 'cam_STmode'\nlet fs = global.get('fs');\nfilepath = '/home/pi/OpenScan/settings/';\ncontent = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});\n\nmsg.enabled = true\nglobal.set('ST',msg.payload)\nif (msg.payload == false){\n global.set('focus1',0)\n global.set('focus2',0)\n file = 'cam_focus1'\n content = String(0)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n file = 'cam_focus2'\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n \n \n msg.enabled = false\n}\nreturn msg\n", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 430, - "y": 1600, + "x": 490, + "y": 1140, "wires": [ [ - "25c4138bddb77b6b", - "7889245e91ddea4b", - "2dd2503d7ab0214b", - "4cfada2de1c5bb74" + "579f2211199fd6ab" ] ] }, { - "id": "ef62086d10d830fd", + "id": "1daf9e3a5bd5ab48", "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "msg.payload = false\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 150, - "y": 1560, + "x": 430, + "y": 1040, "wires": [ [ - "b6f37e23f2491639", - "523019d0a2c698f5" + "fb13752beddee9f2" ] ] }, { - "id": "06504f47ee1744d7", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 12, - "width": 2, - "height": 1, - "name": "", - "label": "Stacksize:", - "format": "{{msg.stacksize}}", - "layout": "row-center", - "className": "", - "x": 710, - "y": 1760, - "wires": [] - }, - { - "id": "2dd2503d7ab0214b", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "a1d29e56599da0bd" - ], - "x": 535, - "y": 1620, - "wires": [] - }, - { - "id": "21306d6402225553", + "id": "6d15f717d5a11002", "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.stacksize = msg.payload\nmsg.enabled = true\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 550, - "y": 1720, + "x": 280, + "y": 1000, "wires": [ [ - "06504f47ee1744d7", - "ca184d58f7deb4b1", - "84608db962fd9932" + "e9b13dfd9f8d3711" ] ] }, { - "id": "e2f8fdd47bdd1b66", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "stacksize", - "label": " ", - "tooltip": "", - "group": "90223f7ddc082321", - "order": 13, - "width": 2, - "height": 1, - "passthru": true, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "2", - "max": "20", - "step": "1", - "className": "", - "x": 400, - "y": 1720, + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, "wires": [ [ - "21306d6402225553" + "1daf9e3a5bd5ab48", + "795c85ad4f109567" ] ] }, { - "id": "523019d0a2c698f5", + "id": "afe47a9eaae6f67f", "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 5, - "width": 1, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, "height": 1, "name": "", - "label": "St", - "format": "", - "layout": "col-center", - "className": "", - "x": 290, - "y": 1560, - "wires": [] - }, - { - "id": "dfbfe28bac5c4221", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 3, - "width": 1, - "height": 1, - "name": "MF", - "label": "MF", - "format": "", - "layout": "col-center", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", "className": "", - "x": 150, - "y": 1440, - "wires": [] - }, - { - "id": "ca184d58f7deb4b1", - "type": "function", - "z": "1613373abaf77a2c", - "name": "save", - "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.stacksize)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 690, - "y": 1720, - "wires": [ - [] - ] + "x": 340, + "y": 40, + "wires": [] }, { - "id": "704a9f89089d1f25", + "id": "8608517d0567d63f", "type": "function", - "z": "1613373abaf77a2c", - "name": "save", - "func": "var file = 'cam_focus'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.focus)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 1110, - "y": 1500, + "x": 190, + "y": 40, "wires": [ - [] + [ + "afe47a9eaae6f67f" + ] ] }, { - "id": "5f8b90ef08a7d68c", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 1720, + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, "wires": [ [ - "e2f8fdd47bdd1b66" + "8608517d0567d63f" ] ] }, { - "id": "4cfada2de1c5bb74", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "if (msg.enabled == true){\n msg.enabled = false\n}\nelse{\n msg.enabled = true\n}\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 570, - "y": 1660, + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, "wires": [ [ - "84608db962fd9932" + "9774e7ad3b506354" ] ] }, { - "id": "84608db962fd9932", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "149e2e46b9623a2d" - ], - "x": 675, - "y": 1660, - "wires": [] - }, - { - "id": "e89c16809f8a5f1c", + "id": "9774e7ad3b506354", "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "gphoto", - "func": "\nfrom OpenScan import camera, save, load_str\n\nif load_str('camera') == 'gphoto':\n if camera('/gphoto_init') == 200:\n if camera('/gphoto_test') == 200:\n save('status_internal_cam','--READY--')\n return msg\nif load_str('camera') == 'external':\n save('status_internal_cam','--READY--')", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", "outputs": 1, - "x": 490, - "y": 100, + "x": 510, + "y": 380, "wires": [ - [] + [ + "f0af909f3e739b22" + ] ] }, { - "id": "917a194be245384a", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable projectname", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 540, + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, "wires": [ [ - "f4b3112a9ec6c487" + "fa181d22775c2ce6" ] ] }, { - "id": "65cef204b16f8741", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable shutter", - "links": [ - "2d76e5617f13cd6c", - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 580, + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, "wires": [ [ - "84d6b96c8ebaac96" + "c615034ea6b26174" ] ] }, { - "id": "2aea1727dbea76ce", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable cropx", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 620, + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, "wires": [ [ - "9c6b48b7b4cc4e1a" + "237c2135cdad86ea" ] ] }, { - "id": "4f212b44aa487945", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable cropy", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 660, + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, "wires": [ [ - "c470fd0b15356206" + "180476141c2a44ad" ] ] }, { - "id": "6d1e12f51f9af0b6", + "id": "5baf89a2682265f7", "type": "link in", - "z": "1613373abaf77a2c", - "name": "start camchk", + "z": "481edaf6db5a7a54", + "name": "enable led", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "eb1a2387a1eeea76" ], - "x": 55, - "y": 100, + "x": 145, + "y": 880, "wires": [ [ - "12f1399b240830bf" + "dd7fb8791d34c751" ] ] }, { - "id": "8ebd1dcb5db156ed", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 2, - "width": 6, - "height": 1, - "name": "", - "label": "Current Status:", - "format": " {{msg.payload}} ", - "layout": "row-spread", - "className": "", - "x": 320, - "y": 160, - "wires": [] - }, - { - "id": "94a7aec739f9266b", + "id": "6a26e8a7253d708c", "type": "function", - "z": "1613373abaf77a2c", - "name": "loadS", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 160, + "x": 830, + "y": 40, "wires": [ [ - "8ebd1dcb5db156ed" + "39c744466a21735e" ] ] }, { - "id": "2415272f42ce468c", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start status", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 55, - "y": 160, + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, "wires": [ [ - "94a7aec739f9266b" + "61aab497fa50898e" ] ] }, { - "id": "a1e14624058e74cd", + "id": "9fd259de91de1da1", "type": "link in", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "start routine settings", "links": [ "960912e90ba5b5bc", "50eeb3e362f9027f" ], - "x": 55, - "y": 500, + "x": 735, + "y": 40, "wires": [ [ - "f4b3112a9ec6c487", - "107a030938cbfea9", - "84d6b96c8ebaac96", - "9c6b48b7b4cc4e1a", - "c470fd0b15356206", - "9e30e33a1520fee0", - "79ecb889f7113405" + "6a26e8a7253d708c", + "35ad7e55833836c1" ] ] }, { - "id": "1daf9e3a5bd5ab48", + "id": "fa181d22775c2ce6", "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "global.set('flag_pw', true)\nglobal.set('flag', true)\nmsg.enabled = true\nreturn msg\n", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 810, - "y": 920, + "x": 1150, + "y": 40, "wires": [ [ - "fb13752beddee9f2" + "ae5ee8787145906d" ] ] }, { - "id": "6d15f717d5a11002", + "id": "c615034ea6b26174", "type": "function", - "z": "1613373abaf77a2c", - "name": "disable", - "func": "msg.enabled = false\nreturn msg\n", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 300, - "y": 840, + "x": 1150, + "y": 80, "wires": [ [ - "e9b13dfd9f8d3711" + "ae5ee8787145906d" ] ] }, { - "id": "d14bbbb446d45e39", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "preview", - "links": [ - "f20da2fc4978b7bf" - ], - "x": 135, - "y": 1260, + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, "wires": [ - [ - "95439678bb2df2a2" - ] + [] ] }, { - "id": "db7eea74d3bf892b", - "type": "ui_toast", - "z": "1613373abaf77a2c", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Finish", - "cancel": "2nd pass", - "raw": false, - "className": "", - "topic": "", + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", "name": "", - "x": 510, - "y": 880, + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, "wires": [ [ - "0b8661103366f834" + "710fc2dbb5ef0167" ] ] }, { - "id": "0b8661103366f834", + "id": "710fc2dbb5ef0167", "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "continue", - "func": "from os import system\nfrom os.path import isfile\n\n\nif msg['payload'] == '2nd pass':\n msg['enabled'] = True\n return msg,None\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp/tmp.jpg'\nzippath = basepath + 'tmp/tmp.zip'\nprojectcode = msg['projectcode']\n\nsystem('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')\n\nmsg['path'] = basepath + 'scans/' + projectcode + '.zip'\n\nif isfile(zippath):\n system('rm ' + zippath)\n\nreturn None, msg", - "outputs": 2, - "x": 660, - "y": 920, + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, "wires": [ [ - "431f917c2541ae48", - "579f2211199fd6ab", - "c433515042ba01b5" - ], - [ - "1daf9e3a5bd5ab48", - "579f2211199fd6ab", - "c433515042ba01b5" + "78cfe60013a1bea4" ] ] }, { - "id": "79ecb889f7113405", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "inactive", - "func": "from requests import get\nfrom OpenScan import load_int\n\ntimeout = load_int('timeout_ringlight')\n\nmsg['cmd'] = 'get'\n\ntry:\n flask = 'http://127.0.0.1:1312/ping'\n r = get(flask, params=msg)\n\n idle = float(r.text.split(\":\")[1].split('}')[0])\n\n msg['payload'] = idle\n\n if idle > timeout:\n return msg,msg\nexcept:\n pass\n\nreturn None,msg", - "outputs": 2, - "x": 200, - "y": 740, + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, "wires": [ [ - "9e30e33a1520fee0" - ], - [ - "8d7e04531c34f349" + "5e83b653850fa16e" ] ] }, { - "id": "8d7e04531c34f349", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "", - "pauseType": "delay", - "timeout": "30", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, - "x": 200, - "y": 780, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, "wires": [ [ - "79ecb889f7113405" + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" ] ] }, { - "id": "c433515042ba01b5", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "pause", - "group": "7aaf184330605300", - "order": 22, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-pause", - "payload": " ", - "payloadType": "str", - "topic": "Scan paused", - "topicType": "str", - "x": 810, - "y": 1040, + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, "wires": [ [ - "63db399d8ac2acb6" + "ed2974731fb8a84e" ] ] }, { - "id": "63db399d8ac2acb6", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "pause", - "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nif status == 'Routine-paused':\n save('status_internal_cam', 'Routine-continue')\nelse:\n save('status_internal_cam', 'Routine-paused')", - "outputs": 1, - "x": 930, - "y": 1040, + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, "wires": [ - [] + [ + "ed2974731fb8a84e" + ] ] }, { - "id": "44c598049cd533fd", + "id": "c8b93b42c720b9cf", "type": "link in", - "z": "1613373abaf77a2c", - "name": "crop", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", "links": [ - "f358de1e64b491bb" + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" ], - "x": 595, - "y": 640, + "x": 85, + "y": 380, "wires": [ [ - "fecf5cff888bb570", - "0ee4950bd21498bd" + "78cfe60013a1bea4" ] ] }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, { "id": "ea54fcc2.cfcc2", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "get dirs", "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", "outputs": 1, @@ -3568,121 +2693,37 @@ "y": 180, "wires": [ [ - "b9a3a0f9.bcbea", - "f3662f8c7d3d7a2d" + "f3662f8c7d3d7a2d", + "01e4783e148c6698" ] ] }, { "id": "2f4c0f98.dee2", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "filelist", "links": [ + "50eeb3e362f9027f", "960912e90ba5b5bc", "a4f09e25.02569", "ed35109311335099", - "fb13752beddee9f2", - "50eeb3e362f9027f" + "fb13752beddee9f2" ], "x": 355, - "y": 140, + "y": 220, "wires": [ [ "ea54fcc2.cfcc2" ] ] }, - { - "id": "b9a3a0f9.bcbea", - "type": "ui_table", - "z": "4981d84ef1a366d1", - "group": "b5fdd57b.15eda8", - "name": "", - "order": 1, - "width": 13, - "height": 7, - "columns": [ - { - "field": "Date", - "title": "Date", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Name", - "title": "Name", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Photos", - "title": "Photos", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Duration", - "title": "ΔT", - "width": "60", - "align": "left", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Size", - "title": "Size", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Status", - "title": "Status", - "width": "140", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - } - ], - "outputs": 1, - "cts": true, - "x": 610, - "y": 180, - "wires": [ - [ - "50710948.71c308", - "4082b136.dae18", - "834046a4.647938", - "0c387c0291d6c131" - ] - ] - }, { "id": "952ce286.4ffd4", "type": "ui_text", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", - "order": 3, + "order": 4, "width": 6, "height": 1, "name": "Status", @@ -3697,7 +2738,7 @@ { "id": "d4383424.7807c8", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "upload", "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", "outputs": 2, @@ -3716,7 +2757,7 @@ { "id": "50710948.71c308", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "set", "rules": [ { @@ -3744,9 +2785,9 @@ { "id": "834046a4.647938", "type": "ui_text", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", - "order": 4, + "order": 5, "width": 6, "height": 1, "name": "Set", @@ -3761,7 +2802,7 @@ { "id": "9a132ab1.b21658", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "flag.true", "rules": [ { @@ -3788,9 +2829,9 @@ { "id": "3c67e97b.9d19a6", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "enable", - "func": "if (global.get('flag') === false){\n msg.enabled = false\n msg.color=\"white\"\n}\nelse{\n msg.enabled = true\n msg.color=\"red\"\n \n}\n\nreturn msg", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -3811,7 +2852,7 @@ { "id": "bfc01f26.c32cf", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "flag.false", "rules": [ { @@ -3838,7 +2879,7 @@ { "id": "b33d604c.5f1a6", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "enable cloud", "links": [ "4082b136.dae18", @@ -3859,7 +2900,7 @@ { "id": "f6bd1a04.470838", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "set", "rules": [ { @@ -3886,7 +2927,7 @@ { "id": "4082b136.dae18", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ "b33d604c.5f1a6", @@ -3899,7 +2940,7 @@ { "id": "f20f2dbc.0f123", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -3914,7 +2955,7 @@ { "id": "8689e938.dd9e38", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -3929,7 +2970,7 @@ { "id": "15de0ebb.616d61", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -3952,7 +2993,7 @@ { "id": "a7d89487.ee8858", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "del", "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", "outputs": 1, @@ -3967,16 +3008,11 @@ { "id": "a4f09e25.02569", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", + "mode": "link", "links": [ - "2f4c0f98.dee2", - "c20357dd.374108", - "e9aab326.a6896", - "edd22cc7.befe1", - "19b81967.49db87", - "8ee1b3bb.7b0b3", - "d5246b3cc796afc6" + "2f4c0f98.dee2" ], "x": 775, "y": 360, @@ -3985,7 +3021,7 @@ { "id": "7a93d1e18254685c", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -3999,7 +3035,7 @@ { "id": "4d99c601c9881680", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "refresh", "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", "outputs": 2, @@ -4018,7 +3054,7 @@ { "id": "372e95797a3f2f3b", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "limit :)", "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", "outputs": 1, @@ -4033,7 +3069,7 @@ { "id": "573edbfdb7500ddc", "type": "delay", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "pauseType": "rate", "timeout": "5", @@ -4058,7 +3094,7 @@ { "id": "dacb1f078b624e10", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4081,7 +3117,7 @@ { "id": "92c98e6ce7cd25f9", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ "7a93d1e18254685c", @@ -4098,7 +3134,7 @@ { "id": "3d16b3789632784d", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4119,7 +3155,7 @@ { "id": "6434e713f088012b", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4140,9 +3176,9 @@ { "id": "c8d65cc7c2ff7c36", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if os.path.isdir(dir + i):\n shutil.rmtree(dir + i)\n else:\n os.remove(dir + i)\n\nreturn msg", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", "outputs": 1, "x": 690, "y": 340, @@ -4155,7 +3191,7 @@ { "id": "f4e9a4bd79b4221f", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "msg", "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", "outputs": 1, @@ -4174,7 +3210,7 @@ { "id": "2806bf08ea21216d", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "msg", "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", "outputs": 1, @@ -4193,7 +3229,7 @@ { "id": "61990987acd0f263", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ "6c6ef2255a7d39e5" @@ -4209,10 +3245,10 @@ { "id": "e8e488a6dd5d0b33", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "Download", - "order": 6, + "order": 8, "width": 3, "height": 1, "format": "\n
Download\n
\n", @@ -4230,7 +3266,7 @@ { "id": "0c387c0291d6c131", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "msg", "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", "outputs": 1, @@ -4249,7 +3285,7 @@ { "id": "e5f38b4a07a5e278", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ "960912e90ba5b5bc", @@ -4266,7 +3302,7 @@ { "id": "e434ef42bd6b92e8", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "upload2", "order": 7, @@ -4282,19 +3318,18 @@ "y": 460, "wires": [ [ - "f6bd1a04.470838", - "bfc01f26.c32cf" + "f6bd1a04.470838" ] ] }, { "id": "c46e10b9c201913e", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "refresh", - "order": 1, - "width": 3, + "order": 2, + "width": 4, "height": 1, "format": "refresh{{msg.text}}", "storeOutMessages": false, @@ -4314,10 +3349,10 @@ { "id": "d5d840183025d91b", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "del set", - "order": 9, + "order": 11, "width": 2, "height": 1, "format": "delete set", @@ -4337,7 +3372,7 @@ { "id": "ab9e90ab5a53a0dd", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "del ", "order": 10, @@ -4360,10 +3395,10 @@ { "id": "478994f671a3907d", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "combine", - "order": 8, + "order": 9, "width": 2, "height": 1, "format": "combine", @@ -4383,7 +3418,7 @@ { "id": "189c1eed09624a7b", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", "outputs": 1, @@ -4402,7 +3437,7 @@ { "id": "51bfd0fb7b1d292e", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", "outputs": 1, @@ -4419,7 +3454,7 @@ { "id": "da325be8e74179be", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", "outputs": 1, @@ -4434,7 +3469,7 @@ { "id": "ed35109311335099", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -4448,7 +3483,7 @@ { "id": "1493398979a63775", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4471,7 +3506,7 @@ { "id": "ada1b6f7cccc9344", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "mode": "link", "links": [ @@ -4484,7 +3519,7 @@ { "id": "6dd356510c446cf4", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "links": [ "ada1b6f7cccc9344" @@ -4500,11 +3535,12 @@ { "id": "b42e061fb1f1f3d7", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ - "397ab7f44b893c89" + "397ab7f44b893c89", + "3876d5cbd248592b" ], "x": 435, "y": 140, @@ -4513,7 +3549,7 @@ { "id": "b99505440832439f", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "diskspace", "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", "outputs": 1, @@ -4528,7 +3564,7 @@ { "id": "92047434f8e9f927", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4549,7 +3585,7 @@ { "id": "f3662f8c7d3d7a2d", "type": "delay", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "pauseType": "rate", "timeout": "5", @@ -4574,7 +3610,7 @@ { "id": "51579603bce21e98", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "read", "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", "outputs": 1, @@ -4589,10 +3625,10 @@ { "id": "9a5baae623355f9d", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "preview", - "order": 5, + "order": 6, "width": 6, "height": 6, "format": "
\n\n\n
\n", @@ -4610,7 +3646,7 @@ { "id": "85839a17fb7b58b9", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "preview", "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", "outputs": 1, @@ -4623,40 +3659,97 @@ ] }, { - "id": "45058bfcf047e8cc", - "type": "inject", - "z": "4981d84ef1a366d1", + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", "name": "", - "props": [ + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, { - "p": "payload" + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } }, { - "p": "topic", - "vt": "str" + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } } ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 100, - "y": 120, + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, "wires": [ - [] + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] ] }, { - "id": "40dee936a9abac0d", + "id": "cb3437ec113e1b6f", "type": "ui_switch", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "label": "SSH", "tooltip": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 3, "width": 6, "height": 1, @@ -4676,21 +3769,21 @@ "animate": false, "className": "", "x": 390, - "y": 340, + "y": 360, "wires": [ [ - "dc354c54078ca607" + "c24f61b87e3226dd" ] ] }, { - "id": "4fd9bb53fdb51a25", + "id": "60fd0adce1cfeb82", "type": "ui_switch", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "label": "Samba", "tooltip": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 4, "width": 6, "height": 1, @@ -4710,32 +3803,32 @@ "animate": false, "className": "", "x": 400, - "y": 380, + "y": 400, "wires": [ [ - "b0aa8ffae5a3578a" + "441d3ef525e901da" ] ] }, { - "id": "dc354c54078ca607", + "id": "c24f61b87e3226dd", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ssh", "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", "outputs": 1, "x": 530, - "y": 340, + "y": 360, "wires": [ [] ] }, { - "id": "52858b4eceacc902", + "id": "c013e836e759a085", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 2, "width": 6, "height": 1, @@ -4751,45 +3844,17 @@ "topic": "topic", "topicType": "msg", "x": 120, - "y": 300, - "wires": [ - [ - "f99ec8781a33ec7d" - ] - ] - }, - { - "id": "595153429adef33b", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Wifi", - "group": "0fe66c9190b8a87c", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Network Settings

Hostname

The device can be accessed through any browser in the same network. Therefore, you can either enter the device's IP address or the given hostname. The standard name is 'openscan' but it is highly recommended to change the name, when using multiple devices (e.g. 'openscan1', 'openscan2' ...)

Select Wifi

After booting, the device will automatically search for available wireless networks and create a list. You can connect to a given network by entering the wifi password and country code. To find the right two-character country code, see the following list: ISO 3166 Country Code on Wikipedia

Search Wifi

You can manually refresh the list of available networks by pressing this button.

Reset Wifi

Delete the list of known wireless networks (and passwords) and reset the default. After this step, you will either need to use Ethernet or a modified wpa_supplicant.conf file. (see glennklockwood.com for more details about the wpa_supplicant.conf file, which has to be manually created and placed into the /boot/ directory of the sd-card)

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 590, - "y": 120, + "y": 320, "wires": [ [ - "f304680180a23479" + "b78346ca3ce70c68" ] ] }, { - "id": "7dc39bd847d16ded", + "id": "f0d8dbcca76a1926", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4802,19 +3867,19 @@ "topic": "", "name": "", "x": 410, - "y": 300, + "y": 320, "wires": [ [ - "5f849178998d9082" + "e95b86cbac1b03b9" ] ] }, { - "id": "02858034e17b827f", + "id": "34374044c0030625", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "General", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 1, "width": 6, "height": 1, @@ -4830,19 +3895,19 @@ "topic": "topic", "topicType": "msg", "x": 740, - "y": 240, + "y": 220, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "675d4933a44ae6b5", + "id": "b2b6bf23c9989133", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Pinout", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 1, "width": 6, "height": 1, @@ -4858,219 +3923,32 @@ "topic": "topic", "topicType": "msg", "x": 430, - "y": 200, + "y": 220, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "b0aa8ffae5a3578a", + "id": "441d3ef525e901da", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "smb", "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", "outputs": 1, "x": 530, - "y": 380, - "wires": [ - [] - ] - }, - { - "id": "cc3cb10f2ea3f8b8", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "blink Light1", - "func": "import RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nfrom OpenScan import ringlight\nfrom time import sleep\n\ndelay = 0.1\nringlight(2,False)\n\nfor i in range (5):\n ringlight(1,True)\n sleep(delay)\n ringlight(1,False)\n sleep(delay)", - "outputs": 1, - "x": 290, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "d114f4d4d7f31981", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "reboot", - "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", - "outputs": 1, - "x": 270, - "y": 720, + "y": 400, "wires": [ [] ] }, { - "id": "79181ad3b56d5c62", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "4fe6b4c0ade0938a", - "order": 7, - "width": 2, - "height": 1, - "name": "", - "label": "Model", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 620, - "wires": [] - }, - { - "id": "4d81bd138733c410", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "4fe6b4c0ade0938a", - "order": 9, - "width": 2, - "height": 1, - "name": "", - "label": "Camera", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 840, - "y": 420, - "wires": [] - }, - { - "id": "80b579a4220e5c23", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "model", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "4fe6b4c0ade0938a", - "order": 8, - "width": 4, - "height": 1, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Please Select", - "value": "None", - "type": "str" - }, - { - "label": "OpenScan Mini", - "value": "OSMini", - "type": "str" - }, - { - "label": "OpenScan Classic", - "value": "OSClassic", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 390, - "y": 620, - "wires": [ - [ - "896242c5a7e50fa7" - ] - ] - }, - { - "id": "a2c1dba3e67be015", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "Camera", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "4fe6b4c0ade0938a", - "order": 10, - "width": 4, - "height": 1, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Pi Cam v1 - 5mp", - "value": "ov5647", - "type": "str" - }, - { - "label": "Pi Cam v2 - 8mp", - "value": "imx219", - "type": "str" - }, - { - "label": "Pi Cam HQ - 12.3mp", - "value": "imx477", - "type": "str" - }, - { - "label": "Arducam IMX519 - 16mp", - "value": "imx519", - "type": "str" - }, - { - "label": "IMX290 a", - "value": "imx290a", - "type": "str" - }, - { - "label": "IMX290 b", - "value": "imx290b", - "type": "str" - }, - { - "label": "IMX378", - "value": "imx378", - "type": "str" - }, - { - "label": "OV9281", - "value": "ov9281", - "type": "str" - }, - { - "label": "DSLR (gphoto)", - "value": "gphoto", - "type": "str" - }, - { - "label": "USB Webcam", - "value": "usb_webcam", - "type": "str" - }, - { - "label": "External Camera", - "value": "external", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 400, - "y": 420, - "wires": [ - [ - "4058a31e942e8f95", - "6d68cccec646e0a0" - ] - ] - }, - { - "id": "9cf5d56263caada7", + "id": "3256bab150113a48", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Motor", - "group": "d49a6dfd7fb17096", + "group": "7a3279eea439bcdd", "order": 1, "width": 6, "height": 1, @@ -5086,19 +3964,19 @@ "topic": "topic", "topicType": "msg", "x": 430, - "y": 120, + "y": 140, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "72238e6a01d1152c", + "id": "7a186669a17daa71", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "camera", - "group": "93aadb71dee6d977", + "group": "d324f0b852c2df0a", "order": 1, "width": 6, "height": 1, @@ -5114,71 +3992,41 @@ "topic": "topic", "topicType": "msg", "x": 420, - "y": 160, + "y": 180, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "15a0a2f431ce55c3", + "id": "edac7dd292e7e486", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "General Settings", "info": "", "x": 120, - "y": 260, - "wires": [] - }, - { - "id": "87a403b9a09aa38d", - "type": "comment", - "z": "017bd4e4a428bee5", - "name": "Network", - "info": "", - "x": 100, - "y": 880, + "y": 280, "wires": [] }, { - "id": "896242c5a7e50fa7", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "model", - "func": "from OpenScan import load_str, save\n\nstate = msg['payload']\nmsg['state'] = state\n\nif state != load_str('model'):\n save('model', state)\n if state == 'OSMini':\n save('rotor_stepsperrotation',48000)\n save('cam_rotation',90)\n save('rotor_anglemin',-70)\n save('rotor_anglemax',20)\n \n\n if state == 'OSClassic':\n save('rotor_stepsperrotation',17067)\n save('cam_rotation',0)\n save('rotor_anglemin',-30)\n save('rotor_anglemax',30)\n\nif state == \"OSMini\":\n msg['crop2'] = 'Crop X (%)'\n msg['crop1'] = 'Crop Y (%)'\nelif state == \"OSClassic\":\n msg['crop1'] = 'Crop X (%)'\n msg['crop2'] = 'Crop Y (%)'\n\nreturn msg", - "outputs": 1, - "x": 530, - "y": 620, - "wires": [ - [ - "f358de1e64b491bb" - ] - ] - }, - { - "id": "4058a31e942e8f95", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "camera", - "func": "from OpenScan import load_str, save\nfrom json import load\nstate = msg['payload']\nstate_old = load_str('camera')\n\nif state_old != state:\n save('camera',state)\n return msg", - "outputs": 1, - "x": 540, - "y": 500, - "wires": [ - [ - "34b685aff2080d31" - ] - ] + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] }, { - "id": "c833f6243a059d83", + "id": "f6d6cc35679ede63", "type": "ui_switch", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "more sets", "label": "Advanced Settings", "tooltip": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 5, "width": 6, "height": 1, @@ -5198,71 +4046,47 @@ "animate": false, "className": "", "x": 400, - "y": 660, + "y": 480, "wires": [ [ - "8be8015931c663cc" + "f06a7bcad524e9f9" ] ] }, { - "id": "15fd1c9e5610cb85", + "id": "29745a36fc157f3f", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "more sets", "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", "outputs": 2, "x": 820, - "y": 660, - "wires": [ - [ - "62cd775a1c02dac8" - ], - [ - "c833f6243a059d83" - ] - ] - }, - { - "id": "74c5c7cd2681045b", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "load camera&model", - "func": "from OpenScan import load_str, load_bool\n\nmodel = load_str('model')\ncamera = load_str('camera')\nupdate = load_bool('updateable')\nmsg['model'] = model\nmsg['camera'] = camera\nmsg2 = {}\nmsg3 = {}\nmsg4 = {}\n\nif camera in ('imx219','ov5647','imx477','imx290a','imx290b','imx378','ov9281','gphoto'):\n msg['payload'] = {\"group\":{\"hide\":[\"Scan_Arducam\"],\"show\":[\"Scan_Settings\",\"Scan_Picamera\"]}}\nelif camera in ('imx519'):\n msg['payload'] = {\"group\":{\"hide\":[\"Scan_Picamera\"],\"show\":[\"Scan_Settings\",\"Scan_Arducam\"]}}\nelif camera in ('external'):\n msg['payload'] = {\"group\":{\"hide\":[\"Scan_Arducam\",\"Scan_Picamera\"],\"show\":[\"Scan_Settings\"]}}\n\n\nif model == 'None' or model == '' or camera == 'None' or camera == '':\n msg2['payload']={\"tabs\": {\"hide\": [\"Scan\", \"Files&Cloud\",\"Settings\",\"Update & Info\"]}}\n msg3['payload'] = {\"group\":{\"hide\":[\"OpenScan_Home\"],\"show\":[\"OpenScan_Initialize\"]}}\nelse:\n msg2['payload']={\"tabs\": {\"show\": [\"Scan\", \"Files&Cloud\",\"Settings\",\"Update & Info\"]},\"hide\":{}}\n msg3['payload'] = {\"group\":{\"show\":[\"OpenScan_Home\"],\"hide\":[\"OpenScan_Initialize\"]}}\n\nif update == True:\n msg4['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg4['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\nreturn msg,msg2,msg3,msg4", - "outputs": 4, - "x": 340, - "y": 40, + "y": 480, "wires": [ [ - "b4db790aad28ba39" - ], - [ - "b4db790aad28ba39" - ], - [ - "b4db790aad28ba39" + "8750ad979e9ea246" ], [ - "b4db790aad28ba39" + "f6d6cc35679ede63" ] ] }, { - "id": "b4db790aad28ba39", + "id": "bf23328f9fb11b22", "type": "ui_ui_control", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "change visibility", "events": "all", "x": 600, - "y": 40, + "y": 60, "wires": [ [] ] }, { - "id": "eb8ccf2786ea3d63", + "id": "b37be1d222bc70c9", "type": "inject", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "1s_repeater", "props": [ { @@ -5281,60 +4105,62 @@ "payload": "", "payloadType": "date", "x": 150, - "y": 40, + "y": 60, "wires": [ [ - "74c5c7cd2681045b", - "9b756a1f9b0e7317" + "89eedf29b404f750" ] ] }, { - "id": "9b756a1f9b0e7317", + "id": "89eedf29b404f750", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "load advanced", - "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\nreturn msg", - "outputs": 1, - "x": 320, - "y": 80, + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, "wires": [ [ - "b4db790aad28ba39" + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" ] ] }, { - "id": "ca4afadb5b21751f", + "id": "2050de5d9e02f69f", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Info Texts", "info": "", "x": 100, - "y": 120, + "y": 140, "wires": [] }, { - "id": "f393400.d87dcc", + "id": "ded3086945a6d4b5", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "check ip address", "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", "outputs": 1, - "x": 410, - "y": 1060, + "x": 250, + "y": 940, "wires": [ [ - "bb789eed.9f73c" + "3cfe464506f46ecd" ] ] }, { - "id": "bb789eed.9f73c", + "id": "3cfe464506f46ecd", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "0fe66c9190b8a87c", - "order": 2, + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, "width": 0, "height": 0, "name": "", @@ -5342,299 +4168,26 @@ "format": "{{msg.ip}}", "layout": "row-spread", "className": "", - "x": 590, - "y": 1060, + "x": 430, + "y": 940, "wires": [] }, { - "id": "2a0f9919.4c9a86", + "id": "bd206ad109831e6a", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "OpenScanCloud", "info": "", "x": 120, - "y": 1240, + "y": 1260, "wires": [] }, { - "id": "27c6b221c90ed9e1", - "type": "exec", - "z": "017bd4e4a428bee5", - "command": "iwlist wlan0 scan | grep ESSID | sed 's/ESSID://g;s/\"//g;s/^ *//;s/ *$//'", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "scan", - "x": 250, - "y": 1040, - "wires": [ - [ - "b05cf92302a5c112", - "f393400.d87dcc" - ], - [ - "e9677b85856b5873" - ], - [] - ] - }, - { - "id": "b05cf92302a5c112", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "WIFI", - "func": "msg['options']=[]\n\nfor i in msg['payload'].split('\\n'):\n if i not in msg['options'] and i!=\"\":\n msg['options'].append(i)\n \nif len(msg['options']) != 0:\n msg['enabled']=True\n\nreturn msg", - "outputs": 1, - "x": 370, - "y": 1020, - "wires": [ - [ - "59c9f67283ba1709" - ] - ] - }, - { - "id": "da5ddaf4cc25b8c8", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "search", - "group": "0fe66c9190b8a87c", - "order": 4, - "width": 3, - "height": 1, - "passthru": false, - "label": "Search Wifi", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "true", - "payloadType": "bool", - "topic": "", - "topicType": "str", - "x": 90, - "y": 980, - "wires": [ - [ - "27c6b221c90ed9e1", - "51521bc6eb44cde5" - ] - ] - }, - { - "id": "59c9f67283ba1709", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "", - "label": "", - "tooltip": "", - "place": "Select Wifi", - "group": "0fe66c9190b8a87c", - "order": 3, - "width": 6, - "height": 1, - "passthru": true, - "multiple": false, - "options": [], - "payload": "", - "topic": "", - "topicType": "str", - "className": "", - "x": 520, - "y": 980, - "wires": [ - [ - "2bb52656f9554dab" - ] - ] - }, - { - "id": "b2d7d6a730f7dca6", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Reset Wifi", - "group": "0fe66c9190b8a87c", - "order": 5, - "width": 3, - "height": 1, - "passthru": false, - "label": "Reset Wifi", - "tooltip": "", - "color": "red", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "Delete all prior wifi connections? (You will need to reconnect to the OpenScan device by Ethernet or manually modify the wpa_supplicant.conf)", - "payloadType": "str", - "topic": "", - "topicType": "str", - "x": 110, - "y": 1140, - "wires": [ - [ - "78985ac6d3bcdf60" - ] - ] - }, - { - "id": "c3b8faac9ebb2c80", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "Reset Wifi", - "func": "from time import sleep\n\nif msg['payload']!=\"Yes\":\n return\n\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\nsleep(3)\nos.system('systemctl restart nodered')\nreturn msg", - "outputs": 1, - "x": 440, - "y": 1140, - "wires": [ - [] - ] - }, - { - "id": "78985ac6d3bcdf60", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 270, - "y": 1140, - "wires": [ - [ - "c3b8faac9ebb2c80" - ] - ] - }, - { - "id": "4f7f49b12c2d2572", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "add Wifi", - "func": "from time import sleep\nsleep(0.1)\n\nos.system('wpa_cli -i wlan0 reconfigure')\n\nreturn msg", - "outputs": 1, - "x": 1320, - "y": 1000, - "wires": [ - [] - ] - }, - { - "id": "ebcc98685059b9d4", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "password", - "x": 780, - "y": 980, - "wires": [ - [ - "68204a14528ab842" - ] - ] - }, - { - "id": "68204a14528ab842", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "if msg['payload'] == 'Cancel':\n return\n\nmsg['password'] = msg['payload']\nmsg['payload']='Enter country code (ISO 3166-1 alpha-2, see: Wikipedia)'\n\n\nreturn msg", - "outputs": 1, - "x": 910, - "y": 980, - "wires": [ - [ - "852edf901bdec9c5" - ] - ] - }, - { - "id": "852edf901bdec9c5", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Save", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "country", - "x": 1040, - "y": 980, - "wires": [ - [ - "1b09d634e3d9357b" - ] - ] - }, - { - "id": "1b09d634e3d9357b", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "modWPA", - "func": "if msg['payload'] == 'Cancel':\n return\n\nif len(msg['payload'])!=2:\n msg['payload'] = 'invalid country code'\n return msg,None\n\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\ntemp_dir = '/home/pi/OpenScan/tmp/wpa'\n\ncode = msg['payload'].upper()\nssid = msg['ssid']\npassword = msg['password']\n\nif len(code) != 2:\n msg['topic'] = 'ERROR'\n msg['payload'] = 'invalid country code (see ISO 3166-1 alpha-2)'\n return msg\n\nwith open(wpa_dir, 'r') as file:\n for i in file.readlines():\n if 'country=' in i:\n code_old=i.split('country=')[1][0:2]\n break\n\nwith open(wpa_dir, 'r') as file:\n wpa = file.read()\n if ssid in wpa:\n msg['topic'] = 'ERROR'\n msg['payload'] = 'Network already exists! If you have trouble connecting, please consider resetting the saved Wifi connections.'\n return msg\n wpa=wpa.replace('country=' + code_old, 'country=' + code)\n wpa=wpa + '\\nnetwork={\\n priority=10\\n ssid=\"'+ssid+'\"\\n psk=\"'+password+'\"\\n}\\n'\n\nwith open(temp_dir,'w+') as file:\n file.write(wpa)\nos.system('mv '+temp_dir + ' ' + wpa_dir)\n\nmsg['topic'] = 'Updating Wifi'\nmsg['payload'] = 'reconnecting might take a moment'\nreturn msg,msg\n", - "outputs": 2, - "x": 1180, - "y": 980, - "wires": [ - [ - "03732a7d3b0c95aa" - ], - [ - "4f7f49b12c2d2572" - ] - ] - }, - { - "id": "03732a7d3b0c95aa", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1330, - "y": 960, - "wires": [ - [] - ] - }, - { - "id": "e97d17c6590138e2", + "id": "b70a9a665c1e4d36", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Cloud-settings", - "group": "3b4bd36726be16d5", + "group": "12b719cba49817c9", "order": 1, "width": 6, "height": 1, @@ -5649,19 +4202,19 @@ "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 620, - "y": 160, + "x": 740, + "y": 260, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "f7bf47e3eec6d736", + "id": "c9f0566601a3e130", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", "order": 4, "width": 0, "height": 0, @@ -5671,14 +4224,14 @@ "layout": "row-spread", "className": "", "x": 410, - "y": 1380, + "y": 1400, "wires": [] }, { - "id": "b52d91c628b151a4", + "id": "9bd86d27ea499a2a", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", "order": 5, "width": 0, "height": 0, @@ -5688,14 +4241,14 @@ "layout": "row-spread", "className": "", "x": 390, - "y": 1420, + "y": 1440, "wires": [] }, { - "id": "1969c709ef2fd1d5", + "id": "2c37f7030810d234", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", "order": 3, "width": 0, "height": 0, @@ -5705,32 +4258,32 @@ "layout": "row-spread", "className": "", "x": 370, - "y": 1460, + "y": 1480, "wires": [] }, { - "id": "88e92b621d2a3394", + "id": "f40286c18afd4501", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "save", "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", "outputs": 2, "x": 750, - "y": 1320, + "y": 1340, "wires": [ [ - "76acd48a511a5e3e", - "b01581296b94dfcd" + "455a5266017ea121", + "50f73cee213ec05c" ], [ - "9c51aa678f16980f" + "264eece408043021" ] ] }, { - "id": "76acd48a511a5e3e", + "id": "455a5266017ea121", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -5742,19 +4295,19 @@ "topic": "", "name": "", "x": 890, - "y": 1280, + "y": 1300, "wires": [ [] ] }, { - "id": "5f50ed3f6ba37cef", + "id": "c368df68593bc2bf", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "label": "Token", "tooltip": "", - "group": "3b4bd36726be16d5", + "group": "12b719cba49817c9", "order": 2, "width": 6, "height": 1, @@ -5766,69 +4319,69 @@ "className": "", "topicType": "str", "x": 350, - "y": 1340, + "y": 1360, "wires": [ [ - "cb62d30728af2968" + "18fd1afa768187b3" ] ] }, { - "id": "cb62d30728af2968", + "id": "18fd1afa768187b3", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Save?", "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", "outputs": 2, "x": 470, - "y": 1340, + "y": 1360, "wires": [ [ - "94e503dd2e64d903" + "418aea2ec65573a0" ], [ - "d859bb39914d4999" + "9792c89c5f4429f9" ] ] }, { - "id": "0dd01eef6e70059e", + "id": "f90a98899b7a71d0", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "text", "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", "outputs": 1, "x": 230, - "y": 1340, + "y": 1360, "wires": [ [ - "5f50ed3f6ba37cef" + "c368df68593bc2bf" ] ] }, { - "id": "788fabff98c7973c", + "id": "b4c843620c251c43", "type": "link in", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "token", "links": [ "960912e90ba5b5bc", - "b01581296b94dfcd", - "d859bb39914d4999", + "50f73cee213ec05c", + "9792c89c5f4429f9", "50eeb3e362f9027f" ], "x": 75, - "y": 1340, + "y": 1360, "wires": [ [ - "0dd01eef6e70059e" + "f90a98899b7a71d0" ] ] }, { - "id": "94e503dd2e64d903", + "id": "418aea2ec65573a0", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -5841,75 +4394,75 @@ "topic": "", "name": "", "x": 610, - "y": 1320, + "y": 1340, "wires": [ [ - "88e92b621d2a3394" + "f40286c18afd4501" ] ] }, { - "id": "d859bb39914d4999", + "id": "9792c89c5f4429f9", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "mode": "link", "links": [ - "788fabff98c7973c" + "b4c843620c251c43" ], "x": 555, - "y": 1360, + "y": 1380, "wires": [] }, { - "id": "9c51aa678f16980f", + "id": "264eece408043021", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "links": [ "5d267acc10020091", - "397ab7f44b893c89" + "3876d5cbd248592b" ], "x": 835, - "y": 1360, + "y": 1380, "wires": [] }, { - "id": "397ab7f44b893c89", + "id": "3876d5cbd248592b", "type": "link in", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "OSCparameters", "links": [ "960912e90ba5b5bc", - "9c51aa678f16980f", + "264eece408043021", "b42e061fb1f1f3d7", "50eeb3e362f9027f" ], "x": 75, - "y": 1380, + "y": 1400, "wires": [ [ - "a7fd00943edc380b" + "5daca3ec47f8e7fc" ] ] }, { - "id": "b01581296b94dfcd", + "id": "50f73cee213ec05c", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "links": [ - "788fabff98c7973c", + "b4c843620c251c43", "5d267acc10020091" ], "x": 835, - "y": 1320, + "y": 1340, "wires": [] }, { - "id": "bf6d941ad307ce22", + "id": "95578e54a9b61cba", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "prompt", "displayTime": "3", "highlight": "", @@ -5922,35 +4475,35 @@ "topic": "", "name": "", "x": 250, - "y": 1520, + "y": 1540, "wires": [ [ - "f22dfef37d5de773" + "d7a5693da7855da8" ] ] }, { - "id": "f22dfef37d5de773", + "id": "d7a5693da7855da8", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", "outputs": 2, "x": 390, - "y": 1520, + "y": 1540, "wires": [ [ - "54602ee49ca022e7" + "2b02b97dd1614e52" ], [ - "1505f3e72f971081" + "183a629accb417b1" ] ] }, { - "id": "1505f3e72f971081", + "id": "183a629accb417b1", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -5963,15 +4516,15 @@ "topic": "", "name": "", "x": 530, - "y": 1560, + "y": 1580, "wires": [ [] ] }, { - "id": "54602ee49ca022e7", + "id": "2b02b97dd1614e52", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "prompt", "displayTime": "3", "highlight": "", @@ -5984,17 +4537,17 @@ "topic": "", "name": "", "x": 530, - "y": 1520, + "y": 1540, "wires": [ [ - "f9efcb87b74abbd4" + "3e4c15d7b538f816" ] ] }, { - "id": "510dbe4d76253bd6", + "id": "3bf622f344172721", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "prompt", "displayTime": "3", "highlight": "", @@ -6007,35 +4560,35 @@ "topic": "", "name": "", "x": 810, - "y": 1520, + "y": 1540, "wires": [ [ - "600b2306caed1640" + "e431cb2b8d217cee" ] ] }, { - "id": "600b2306caed1640", + "id": "e431cb2b8d217cee", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", "outputs": 1, "x": 950, - "y": 1520, + "y": 1540, "wires": [ [ - "bbad1ab5f8f63fb7" + "106874534890f229" ] ] }, { - "id": "d34cd203725bac15", + "id": "a38d7fde5c73210f", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Register", - "group": "3b4bd36726be16d5", - "order": 7, + "group": "12b719cba49817c9", + "order": 6, "width": 2, "height": 1, "passthru": false, @@ -6050,17 +4603,17 @@ "topic": "Requesting an OpenScanCloud Token", "topicType": "str", "x": 100, - "y": 1520, + "y": 1540, "wires": [ [ - "bf6d941ad307ce22" + "95578e54a9b61cba" ] ] }, { - "id": "bbad1ab5f8f63fb7", + "id": "106874534890f229", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -6073,67 +4626,67 @@ "topic": "", "name": "", "x": 1090, - "y": 1520, + "y": 1540, "wires": [ [] ] }, { - "id": "a7fd00943edc380b", + "id": "5daca3ec47f8e7fc", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", "outputs": 1, "x": 230, - "y": 1380, + "y": 1400, "wires": [ [ - "f7bf47e3eec6d736", - "b52d91c628b151a4", - "1969c709ef2fd1d5" + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" ] ] }, { - "id": "124459147143ec6a", + "id": "f34de19d4cf810a9", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Motor", "info": "", "x": 90, - "y": 1600, + "y": 1740, "wires": [] }, { - "id": "dbd62b91a6c9c412", + "id": "26c2b58e21f97475", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Camera", "info": "", "x": 90, - "y": 2240, + "y": 2500, "wires": [] }, { - "id": "842b6fe016087ce3", + "id": "a8ec972bad47a9a8", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Pinout", "info": "", - "x": 110, - "y": 2860, + "x": 90, + "y": 2960, "wires": [] }, { - "id": "8c1a92f2dcc976c7", + "id": "b03e8b51187e88eb", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Rotor_delay (ms)", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 14, + "group": "7a3279eea439bcdd", + "order": 16, "width": 3, "height": 1, "passthru": false, @@ -6145,22 +4698,22 @@ "step": "0.005", "className": "", "x": 450, - "y": 1840, + "y": 2100, "wires": [ [ - "bb54bbdae6690576" + "11fd3363416433f9" ] ] }, { - "id": "2647111c06f2055d", + "id": "6aae9d4fddf08cc0", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt delay", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 27, + "group": "7a3279eea439bcdd", + "order": 30, "width": 3, "height": 1, "passthru": false, @@ -6172,22 +4725,22 @@ "step": "0.005", "className": "", "x": 420, - "y": 2080, + "y": 2340, "wires": [ [ - "fb8145a9f8d4f7b2" + "e50492d1e18f43c6" ] ] }, { - "id": "f9b51424edb0491c", + "id": "543e1690693acbeb", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_acc", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 16, + "group": "7a3279eea439bcdd", + "order": 18, "width": 3, "height": 1, "passthru": false, @@ -6199,22 +4752,22 @@ "step": "0.1", "className": "", "x": 420, - "y": 1880, + "y": 2140, "wires": [ [ - "ea87ecfd2af3cc7f" + "e8b24efb0f30288e" ] ] }, { - "id": "1ab34b0a78b2c577", + "id": "9a56c087d941f1da", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_accramp", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 18, + "group": "7a3279eea439bcdd", + "order": 20, "width": 3, "height": 1, "passthru": false, @@ -6226,22 +4779,22 @@ "step": "100", "className": "", "x": 440, - "y": 1920, + "y": 2180, "wires": [ [ - "249f44c3a87793ba" + "29f576be9e292232" ] ] }, { - "id": "1d4230b3d9b93f63", + "id": "dfdebe10dbf0e198", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_stepsperrotation", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 12, + "group": "7a3279eea439bcdd", + "order": 14, "width": 3, "height": 1, "passthru": false, @@ -6252,19 +4805,19 @@ "className": "", "topicType": "msg", "x": 460, - "y": 1800, + "y": 2060, "wires": [ [ - "0bb56b1edb12c2cf" + "78e256083f59f66f" ] ] }, { - "id": "2e3222f0aba88040", + "id": "af8dfe78cbd0c301", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 17, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, "width": 3, "height": 1, "name": "rotor Accramp", @@ -6273,15 +4826,15 @@ "layout": "row-left", "className": "", "x": 780, - "y": 1880, + "y": 2140, "wires": [] }, { - "id": "9d50311679acf215", + "id": "ee4b8908a5b83880", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 11, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, "width": 3, "height": 1, "name": "rotor_Steps per Rotation", @@ -6290,15 +4843,15 @@ "layout": "row-spread", "className": "", "x": 810, - "y": 1920, + "y": 2180, "wires": [] }, { - "id": "25d7b4dd2aab8f05", + "id": "c4deaa38c1b0adbf", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 15, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, "width": 3, "height": 1, "name": "rotor Acc", @@ -6307,15 +4860,15 @@ "layout": "row-left", "className": "", "x": 760, - "y": 1840, + "y": 2100, "wires": [] }, { - "id": "15682cca9622831f", + "id": "baec873a95fff48a", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 13, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, "width": 3, "height": 1, "name": "rotor_delay", @@ -6324,15 +4877,15 @@ "layout": "row-left", "className": "", "x": 770, - "y": 1800, + "y": 2060, "wires": [] }, { - "id": "8e2d22042bfcb4e8", + "id": "355e89ab4e5484e4", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 23, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, "width": 6, "height": 1, "name": "tt", @@ -6341,18 +4894,18 @@ "layout": "row-center", "className": "", "x": 90, - "y": 2040, + "y": 2300, "wires": [] }, { - "id": "56bc3b93af2ebe16", + "id": "10687d331a732790", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_acc", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 29, + "group": "7a3279eea439bcdd", + "order": 32, "width": 3, "height": 1, "passthru": false, @@ -6364,22 +4917,22 @@ "step": "0.1", "className": "", "x": 410, - "y": 2120, + "y": 2380, "wires": [ [ - "35422077b53da9bf" + "af88b9da72917d62" ] ] }, { - "id": "6ef996f8a36f94c2", + "id": "721b9680a3fa460e", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_accramp", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 31, + "group": "7a3279eea439bcdd", + "order": 34, "width": 3, "height": 1, "passthru": false, @@ -6391,22 +4944,22 @@ "step": "1", "className": "", "x": 430, - "y": 2160, + "y": 2420, "wires": [ [ - "2c000bd53cdb98ca" + "b1b4678827d3a6dd" ] ] }, { - "id": "0c50fdbb5ac3c373", + "id": "c6642c7470d3820c", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_stepsperrotation", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 25, + "group": "7a3279eea439bcdd", + "order": 28, "width": 3, "height": 1, "passthru": false, @@ -6417,19 +4970,19 @@ "className": "", "topicType": "msg", "x": 450, - "y": 2040, + "y": 2300, "wires": [ [ - "485a4bed5a6bea23" + "eef89545ec0f6aa8" ] ] }, { - "id": "213ccfb441a42890", + "id": "18e5918748660109", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 30, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, "width": 3, "height": 1, "name": "ttAccramp", @@ -6438,15 +4991,15 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2160, + "y": 2420, "wires": [] }, { - "id": "73c9b4d09dc25e54", + "id": "8e805244dc1899e8", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 24, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, "width": 3, "height": 1, "name": "tt_steps per Rotation", @@ -6455,15 +5008,15 @@ "layout": "row-spread", "className": "", "x": 800, - "y": 2040, + "y": 2300, "wires": [] }, { - "id": "a81824c92f22487d", + "id": "a09e5fbea861bfb1", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 28, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, "width": 3, "height": 1, "name": "tt Acc", @@ -6472,15 +5025,15 @@ "layout": "row-left", "className": "", "x": 750, - "y": 2120, + "y": 2380, "wires": [] }, { - "id": "9715161858f69649", + "id": "7b06448b3b222011", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 26, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, "width": 3, "height": 1, "name": "tt_delay", @@ -6489,18 +5042,18 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2080, + "y": 2340, "wires": [] }, { - "id": "1b3ac50d2c6600c6", + "id": "0dfc86d90258f9bb", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_angle", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 20, + "group": "7a3279eea439bcdd", + "order": 22, "width": 3, "height": 1, "passthru": false, @@ -6512,19 +5065,19 @@ "step": "1", "className": "", "x": 430, - "y": 1960, + "y": 2220, "wires": [ [ - "e0d7c36daa42b3f3" + "c4b5a38c5c1df3d2" ] ] }, { - "id": "6dcd1f0ccb01a299", + "id": "9319d7d4f34c6d22", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 19, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, "width": 3, "height": 1, "name": "rotor_angle", @@ -6533,18 +5086,18 @@ "layout": "row-spread", "className": "", "x": 770, - "y": 1960, + "y": 2220, "wires": [] }, { - "id": "16e9a3a71c4bb916", + "id": "1610895f430b9aca", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_angle", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 33, + "group": "7a3279eea439bcdd", + "order": 36, "width": 3, "height": 1, "passthru": false, @@ -6556,19 +5109,19 @@ "step": "1", "className": "", "x": 420, - "y": 2200, + "y": 2460, "wires": [ [ - "c34111aaec734dd9" + "0f3367983bb8e159" ] ] }, { - "id": "888161059eb9c71c", + "id": "96a9febc0928b6f0", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 32, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, "width": 3, "height": 1, "name": "tt_angle", @@ -6577,15 +5130,15 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2200, + "y": 2460, "wires": [] }, { - "id": "f4fc72297074c7ae", + "id": "e2c5ea8c16a5ea32", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 4, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, "width": 6, "height": 1, "name": "rotor", @@ -6594,18 +5147,18 @@ "layout": "row-center", "className": "", "x": 90, - "y": 1680, + "y": 1820, "wires": [] }, { - "id": "9b1d8f9e21b34102", + "id": "277037c4716d85bf", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_dir", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 35, + "group": "7a3279eea439bcdd", + "order": 38, "width": 3, "height": 1, "passthru": false, @@ -6617,22 +5170,22 @@ "step": "1", "className": "", "x": 410, - "y": 2240, + "y": 2500, "wires": [ [ - "89dbbe7d99ddbbaf" + "c9d2e31514def4fc" ] ] }, { - "id": "b2e839fe47a32b5f", + "id": "1361134e9847f003", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_dir", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 22, + "group": "7a3279eea439bcdd", + "order": 24, "width": 3, "height": 1, "passthru": false, @@ -6644,19 +5197,19 @@ "step": "1", "className": "", "x": 420, - "y": 2000, + "y": 2260, "wires": [ [ - "204b0a5c8629d78a" + "523717b0f218a5fd" ] ] }, { - "id": "4519daf0b4b28aef", + "id": "6b0d58943ecb8bb2", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 34, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, "width": 3, "height": 1, "name": "tt_dir", @@ -6665,15 +5218,15 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2240, + "y": 2500, "wires": [] }, { - "id": "5f269ea2c8a53f6c", + "id": "08f93dd2aeedb391", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 21, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, "width": 3, "height": 1, "name": "rotor_dir", @@ -6682,74 +5235,47 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2000, + "y": 2260, "wires": [] }, { - "id": "b67dfacfc9a23aa5", + "id": "46b91bef44714366", "type": "link in", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "advanced settings", "links": [ - "62cd775a1c02dac8" + "8750ad979e9ea246" ], "x": 95, - "y": 80, + "y": 100, "wires": [ [ - "9b756a1f9b0e7317" + "89eedf29b404f750" ] ] }, { - "id": "62cd775a1c02dac8", + "id": "8750ad979e9ea246", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "mode": "link", "links": [ - "b67dfacfc9a23aa5" + "46b91bef44714366" ], "x": 955, - "y": 660, + "y": 480, "wires": [] }, { - "id": "9d94dbc523d989a3", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_delay_after", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 16, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 450, - "y": 2460, - "wires": [ - [ - "b81e238ccd0a04fe" - ] - ] - }, - { - "id": "0558d6eb9a01862e", + "id": "2522f888dc58972f", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_delay_before", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 14, + "group": "d324f0b852c2df0a", + "order": 7, "width": 3, "height": 1, "passthru": false, @@ -6757,53 +5283,26 @@ "topic": "", "topicType": "str", "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 440, - "y": 2500, - "wires": [ - [ - "a0048747e7300bdc" - ] - ] - }, - { - "id": "d47515c9b208bfb7", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_timeout", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 12, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.01", "max": "1", - "step": "0.01", + "step": "0.02", "className": "", - "x": 420, - "y": 2420, + "x": 430, + "y": 2600, "wires": [ [ - "9b0d5c521a7822cc" + "5c752757090c49d2" ] ] }, { - "id": "89c76766c7552b57", + "id": "30e8df3d616512d8", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_gain", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 22, + "group": "d324f0b852c2df0a", + "order": 11, "width": 3, "height": 1, "passthru": false, @@ -6814,77 +5313,23 @@ "max": "10", "step": "0.1", "className": "", - "x": 410, - "y": 2540, - "wires": [ - [ - "9b26ed02296d27c9" - ] - ] - }, - { - "id": "c385518eb65a1b27", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_awbg_red", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 18, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-10", - "max": "10", - "step": "0.1", - "className": "", - "x": 430, - "y": 2580, - "wires": [ - [ - "b0ac7e9a7c713b84" - ] - ] - }, - { - "id": "5c80833b718d9bf6", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_awbg_blue", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 20, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-10", - "max": "10", - "step": "0.1", - "className": "", - "x": 430, - "y": 2620, + "x": 400, + "y": 2640, "wires": [ [ - "827b1a671a77037d" + "a1769f0277834f6d" ] ] }, { - "id": "5a3826e112fb24e6", + "id": "d855d926df89d65b", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_contrast", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 24, + "group": "d324f0b852c2df0a", + "order": 13, "width": 3, "height": 1, "passthru": false, @@ -6895,23 +5340,24 @@ "max": "5", "step": "0.1", "className": "", - "x": 430, - "y": 2660, + "x": 420, + "y": 2760, "wires": [ [ - "78a1536c167da741" + "1a8b0ba21b4f3005", + "654bc70a18820828" ] ] }, { - "id": "3182ed7ac02b1509", + "id": "7617517dc8ba2859", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_saturation", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 26, + "group": "d324f0b852c2df0a", + "order": 15, "width": 3, "height": 1, "passthru": false, @@ -6922,149 +5368,82 @@ "max": "5", "step": "0.1", "className": "", - "x": 430, - "y": 2700, + "x": 420, + "y": 2800, "wires": [ [ - "fe9a5b68fc8c2077" + "dc8fc962ff7d594b", + "e64feb03a791ca33" ] ] }, { - "id": "7fa6337cdf0a0bc8", + "id": "cbaa23c34e10fae1", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_jpeg_q", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", + "group": "d324f0b852c2df0a", "order": 3, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "100", - "step": "1", - "className": "", - "x": 420, - "y": 2740, - "wires": [ - [ - "e27d2613e942f344" - ] - ] - }, - { - "id": "08275bf96f87b8ef", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 11, - "width": 3, - "height": 1, - "name": "timeout", - "label": "Timeout", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2420, - "wires": [] - }, - { - "id": "d2d028df4a139f41", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 15, - "width": 3, - "height": 1, - "name": "delay_after", - "label": "Delay after", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2460, - "wires": [] - }, - { - "id": "c6a65762aa4ffb7b", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 13, - "width": 3, - "height": 1, - "name": "delay_before", - "label": "Delay before", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2500, - "wires": [] - }, - { - "id": "780323fd4504b855", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 21, - "width": 3, - "height": 1, - "name": "gain", - "label": "Gain", - "format": "", - "layout": "row-spread", + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", "className": "", - "x": 750, - "y": 2540, - "wires": [] + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] }, { - "id": "780bf08b41202135", + "id": "bbe443b039a14e21", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 17, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, "width": 3, "height": 1, - "name": "awbg red", - "label": "AWBG red", + "name": "delay_before", + "label": "Delay before", "format": "", "layout": "row-spread", "className": "", "x": 760, - "y": 2580, + "y": 2600, "wires": [] }, { - "id": "c0faf441fc918538", + "id": "d320ed3d701e6cc2", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 19, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, "width": 3, "height": 1, - "name": "awbg blue", - "label": "AWBG blue", + "name": "gain", + "label": "Gain", "format": "", "layout": "row-spread", "className": "", - "x": 770, - "y": 2620, + "x": 740, + "y": 2640, "wires": [] }, { - "id": "93d12b447a39c5bb", + "id": "f5834dd4646c8af9", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 23, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, "width": 3, "height": 1, "name": "contrast", @@ -7072,16 +5451,16 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 2660, + "x": 750, + "y": 2760, "wires": [] }, { - "id": "e77e6dcd285d3062", + "id": "ae9a4e19469813ef", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 25, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, "width": 3, "height": 1, "name": "saturation", @@ -7089,15 +5468,15 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 2700, + "x": 750, + "y": 2800, "wires": [] }, { - "id": "a7075bc8d5ee1138", + "id": "bd629d0d31233c8b", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", "order": 2, "width": 3, "height": 1, @@ -7106,18 +5485,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2740, + "x": 740, + "y": 2840, "wires": [] }, { - "id": "282681e7c4351f74", + "id": "e89f61dbe6a6cffe", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ext", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 3, "width": 2, "height": 1, @@ -7128,19 +5507,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 2900, + "x": 390, + "y": 3000, "wires": [ [ - "b17e82651407d8e0" + "885bc559fafec5f2" ] ] }, { - "id": "da43c58979737fec", + "id": "ece38cb172a12d75", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 2, "width": 4, "height": 1, @@ -7149,18 +5528,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2900, + "x": 730, + "y": 3000, "wires": [] }, { - "id": "ef70d61678fe1f11", + "id": "70014da0b6ab6698", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "light1", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 5, "width": 2, "height": 1, @@ -7171,19 +5550,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 2940, + "x": 390, + "y": 3040, "wires": [ [ - "2c812acffdb330c5" + "f70321c96bf81360" ] ] }, { - "id": "fec56a7e913b21d6", + "id": "29634ea5f6d666df", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 4, "width": 4, "height": 1, @@ -7192,18 +5571,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2940, + "x": 730, + "y": 3040, "wires": [] }, { - "id": "24929b4629f22070", + "id": "2544963852c6881a", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "light2", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 7, "width": 2, "height": 1, @@ -7214,19 +5593,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 2980, + "x": 390, + "y": 3080, "wires": [ [ - "ae0654af69446942" + "95e1603bbd06a69d" ] ] }, { - "id": "7c6bdc0504aa4cc7", + "id": "27903533cd85a59e", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 6, "width": 4, "height": 1, @@ -7235,18 +5614,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2980, + "x": 730, + "y": 3080, "wires": [] }, { - "id": "8c396b060f3d2646", + "id": "a1394401246eb735", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotordir", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 9, "width": 2, "height": 1, @@ -7257,19 +5636,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3020, + "x": 400, + "y": 3120, "wires": [ [ - "58cf48cfacc979fb" + "a8f92ea6bf394640" ] ] }, { - "id": "97568610daccf74a", + "id": "bc0aa4bacdfa94ea", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 8, "width": 4, "height": 1, @@ -7278,18 +5657,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3020, + "x": 740, + "y": 3120, "wires": [] }, { - "id": "a3c58ea48c388215", + "id": "f15ca4518b5f223e", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotorstep", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 11, "width": 2, "height": 1, @@ -7300,19 +5679,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3060, + "x": 400, + "y": 3160, "wires": [ [ - "c7ae206f2fff6810" + "06397bb46b3bb541" ] ] }, { - "id": "6da92aeaeffd95e0", + "id": "0d2924b160e7e383", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 10, "width": 4, "height": 1, @@ -7321,18 +5700,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3060, + "x": 740, + "y": 3160, "wires": [] }, { - "id": "9b5da90eaf6ac562", + "id": "49900bb9047dd965", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotoren", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 13, "width": 2, "height": 1, @@ -7343,19 +5722,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3100, + "x": 400, + "y": 3200, "wires": [ [ - "cfebd4a47a68b319" + "687dcdc1ede11700" ] ] }, { - "id": "12623e4addfa2c22", + "id": "a4d743ca73ee1622", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 12, "width": 4, "height": 1, @@ -7364,18 +5743,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3100, + "x": 740, + "y": 3200, "wires": [] }, { - "id": "f24cb404d7d09f8a", + "id": "5a90224dc998b417", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ttdir", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 15, "width": 2, "height": 1, @@ -7386,19 +5765,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 3140, + "x": 390, + "y": 3240, "wires": [ [ - "90f4d220928e4727" + "e220740c0d38ccb0" ] ] }, { - "id": "542bfb9d92935c2c", + "id": "67dc1b544c4ddf9f", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 14, "width": 4, "height": 1, @@ -7407,18 +5786,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 3140, + "x": 730, + "y": 3240, "wires": [] }, { - "id": "1f79467df98ce894", + "id": "d2364ab09627fe94", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ttstep", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 17, "width": 2, "height": 1, @@ -7429,19 +5808,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 3180, + "x": 390, + "y": 3280, "wires": [ [ - "b05e1e612887f9c2" + "79d7e5a705ab813a" ] ] }, { - "id": "170d3b925f7745cc", + "id": "145b67ac40721ba6", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 16, "width": 4, "height": 1, @@ -7450,19 +5829,19 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 3180, + "x": 730, + "y": 3280, "wires": [] }, { - "id": "661614f5bd2c71d6", + "id": "eef25405472acfee", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "endstop1", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", - "order": 21, + "group": "70d0be671bf03ca7", + "order": 19, "width": 2, "height": 1, "passthru": false, @@ -7472,20 +5851,20 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3220, + "x": 400, + "y": 3320, "wires": [ [ - "2af447a6905b83bc" + "12d20f2274bcc511" ] ] }, { - "id": "c18b55859dae5f85", + "id": "35eb252a41413531", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", - "order": 20, + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, "width": 4, "height": 1, "name": "endstop1", @@ -7493,19 +5872,19 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3220, + "x": 740, + "y": 3320, "wires": [] }, { - "id": "e23a396162026618", + "id": "74e455136b5ca5dd", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "endstop2", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", - "order": 23, + "group": "70d0be671bf03ca7", + "order": 21, "width": 2, "height": 1, "passthru": false, @@ -7515,20 +5894,20 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3260, + "x": 400, + "y": 3360, "wires": [ [ - "787a128f84f747c0" + "a4a89668ce4c9f05" ] ] }, { - "id": "82c1a33014d003e9", + "id": "3a74f653800eb831", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", - "order": 22, + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, "width": 4, "height": 1, "name": "endstop2", @@ -7536,14 +5915,14 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3260, + "x": 740, + "y": 3360, "wires": [] }, { - "id": "5255759a7c5b2a74", + "id": "5fcef1cb2e9e4788", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -7556,37 +5935,37 @@ "topic": "", "name": "confirm", "x": 680, - "y": 660, + "y": 480, "wires": [ [ - "15fd1c9e5610cb85" + "29745a36fc157f3f" ] ] }, { - "id": "8be8015931c663cc", + "id": "f06a7bcad524e9f9", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", "outputs": 1, "x": 530, - "y": 660, + "y": 480, "wires": [ [ - "5255759a7c5b2a74" + "5fcef1cb2e9e4788" ] ] }, { - "id": "9d464b2ba1edaf48", + "id": "f455fb39039617ae", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_rotation", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 10, + "group": "d324f0b852c2df0a", + "order": 5, "width": 3, "height": 1, "passthru": false, @@ -7597,20 +5976,20 @@ "max": "270", "step": "90", "className": "", - "x": 420, - "y": 2780, + "x": 410, + "y": 2880, "wires": [ [ - "b7d3fe0c0b40b3e1" + "3019576de193d9d6" ] ] }, { - "id": "db98b95693ebce63", + "id": "fdfbc900fe424eb9", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 9, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, "width": 3, "height": 1, "name": "cam_rot", @@ -7618,14 +5997,14 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 2780, + "x": 750, + "y": 2880, "wires": [] }, { - "id": "6659121906897a1f", + "id": "c3699d6b9664ccca", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7634,17 +6013,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1800, + "y": 2060, "wires": [ [ - "1d4230b3d9b93f63" + "dfdebe10dbf0e198" ] ] }, { - "id": "0bb56b1edb12c2cf", + "id": "78e256083f59f66f", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7653,15 +6032,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1800, + "y": 2060, "wires": [ [] ] }, { - "id": "569829eeff715c33", + "id": "0f9141b401322374", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7670,17 +6049,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1920, + "y": 2180, "wires": [ [ - "1ab34b0a78b2c577" + "9a56c087d941f1da" ] ] }, { - "id": "249f44c3a87793ba", + "id": "29f576be9e292232", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7689,15 +6068,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1920, + "y": 2180, "wires": [ [] ] }, { - "id": "c997e60519341afd", + "id": "23e3099b34c4e475", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7706,17 +6085,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1960, + "y": 2220, "wires": [ [ - "1b3ac50d2c6600c6" + "0dfc86d90258f9bb" ] ] }, { - "id": "e0d7c36daa42b3f3", + "id": "c4b5a38c5c1df3d2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7725,15 +6104,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1960, + "y": 2220, "wires": [ [] ] }, { - "id": "59ecf3a22cd3a669", + "id": "79a14162ac805fac", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7742,17 +6121,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2000, + "y": 2260, "wires": [ [ - "b2e839fe47a32b5f" + "1361134e9847f003" ] ] }, { - "id": "204b0a5c8629d78a", + "id": "523717b0f218a5fd", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7761,15 +6140,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2000, + "y": 2260, "wires": [ [] ] }, { - "id": "15f02421b30a9ab6", + "id": "f5cf780f3fa8997e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", "outputs": 1, @@ -7778,17 +6157,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1840, + "y": 2100, "wires": [ [ - "8c1a92f2dcc976c7" + "b03e8b51187e88eb" ] ] }, { - "id": "bb54bbdae6690576", + "id": "11fd3363416433f9", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7797,15 +6176,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1840, + "y": 2100, "wires": [ [] ] }, { - "id": "58928befcc61b1f7", + "id": "02060b3f3b294563", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -7814,17 +6193,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1880, + "y": 2140, "wires": [ [ - "f9b51424edb0491c" + "543e1690693acbeb" ] ] }, { - "id": "ea87ecfd2af3cc7f", + "id": "e8b24efb0f30288e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7833,34 +6212,34 @@ "finalize": "", "libs": [], "x": 630, - "y": 1880, + "y": 2140, "wires": [ [] ] }, { - "id": "27bc56f273360ac7", + "id": "de1ad8b27b72a5ac", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", "outputs": 1, - "noerr": 0, + "noerr": 4, "initialize": "", "finalize": "", "libs": [], "x": 290, - "y": 2040, + "y": 2300, "wires": [ [ - "0c50fdbb5ac3c373" + "c6642c7470d3820c" ] ] }, { - "id": "f46ced86106306c8", + "id": "ed4d587cb4feb064", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7869,17 +6248,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2160, + "y": 2420, "wires": [ [ - "6ef996f8a36f94c2" + "721b9680a3fa460e" ] ] }, { - "id": "4339704cd8552eb3", + "id": "5b02160c33605ae7", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7888,17 +6267,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2200, + "y": 2460, "wires": [ [ - "16e9a3a71c4bb916" + "1610895f430b9aca" ] ] }, { - "id": "1ac53bb6150645fe", + "id": "304c135ec09801e3", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7907,17 +6286,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2240, + "y": 2500, "wires": [ [ - "9b1d8f9e21b34102" + "277037c4716d85bf" ] ] }, { - "id": "9b89eb1eaf333c10", + "id": "a91dcbe0f9a2416a", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", "outputs": 1, @@ -7926,17 +6305,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2080, + "y": 2340, "wires": [ [ - "2647111c06f2055d" + "6aae9d4fddf08cc0" ] ] }, { - "id": "2e8927be0e235fa1", + "id": "6b2eb1cb95e573f9", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -7945,17 +6324,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2120, + "y": 2380, "wires": [ [ - "56bc3b93af2ebe16" + "10687d331a732790" ] ] }, { - "id": "485a4bed5a6bea23", + "id": "eef89545ec0f6aa8", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7964,15 +6343,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2040, + "y": 2300, "wires": [ [] ] }, { - "id": "2c000bd53cdb98ca", + "id": "b1b4678827d3a6dd", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7981,15 +6360,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2160, + "y": 2420, "wires": [ [] ] }, { - "id": "c34111aaec734dd9", + "id": "0f3367983bb8e159", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7998,15 +6377,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2200, + "y": 2460, "wires": [ [] ] }, { - "id": "89dbbe7d99ddbbaf", + "id": "c9d2e31514def4fc", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8015,15 +6394,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2240, + "y": 2500, "wires": [ [] ] }, { - "id": "fb8145a9f8d4f7b2", + "id": "e50492d1e18f43c6", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8032,15 +6411,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2080, + "y": 2340, "wires": [ [] ] }, { - "id": "35422077b53da9bf", + "id": "af88b9da72917d62", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8049,70 +6428,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2120, - "wires": [ - [] - ] - }, - { - "id": "d5308090f2b7971a", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_timeout'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2420, - "wires": [ - [ - "d47515c9b208bfb7" - ] - ] - }, - { - "id": "9b0d5c521a7822cc", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_timeout'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2420, + "y": 2380, "wires": [ [] ] }, { - "id": "694d1068bea15171", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2460, - "wires": [ - [ - "9d94dbc523d989a3" - ] - ] - }, - { - "id": "cec3e5e78a40476b", + "id": "43fe948b3e7234e2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -8120,35 +6444,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2500, + "x": 280, + "y": 2600, "wires": [ [ - "0558d6eb9a01862e" + "2522f888dc58972f" ] ] }, { - "id": "b81e238ccd0a04fe", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2460, - "wires": [ - [] - ] - }, - { - "id": "a0048747e7300bdc", + "id": "5c752757090c49d2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8156,16 +6463,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2500, + "x": 620, + "y": 2600, "wires": [ [] ] }, { - "id": "6f524f9370a18482", + "id": "435681b3f7625a7e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -8173,18 +6480,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2540, + "x": 280, + "y": 2640, "wires": [ [ - "89c76766c7552b57" + "30e8df3d616512d8" ] ] }, { - "id": "9b26ed02296d27c9", + "id": "a1769f0277834f6d", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8192,88 +6499,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2540, - "wires": [ - [] - ] - }, - { - "id": "1f87f473e327c3cc", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_awbg_red'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2580, - "wires": [ - [ - "c385518eb65a1b27" - ] - ] - }, - { - "id": "b0ac7e9a7c713b84", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_awbg_red'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2580, - "wires": [ - [] - ] - }, - { - "id": "cff7ac5f1e061855", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_awbg_blue'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2620, - "wires": [ - [ - "5c80833b718d9bf6" - ] - ] - }, - { - "id": "827b1a671a77037d", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_awbg_blue'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2620, + "x": 620, + "y": 2640, "wires": [ [] ] }, { - "id": "cf854461c37ca54f", + "id": "1de07c7d285cbaf3", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -8281,18 +6516,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2660, + "x": 280, + "y": 2760, "wires": [ [ - "5a3826e112fb24e6" + "d855d926df89d65b" ] ] }, { - "id": "78a1536c167da741", + "id": "1a8b0ba21b4f3005", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8300,16 +6535,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2660, + "x": 620, + "y": 2760, "wires": [ [] ] }, { - "id": "ba10e04dd1761692", + "id": "ebc9e283468eda31", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -8317,18 +6552,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2700, + "x": 280, + "y": 2800, "wires": [ [ - "3182ed7ac02b1509" + "7617517dc8ba2859" ] ] }, { - "id": "fe9a5b68fc8c2077", + "id": "dc8fc962ff7d594b", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8336,16 +6571,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2700, + "x": 620, + "y": 2800, "wires": [ [] ] }, { - "id": "a69d216114f908a5", + "id": "60d641613527c736", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8353,18 +6588,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2740, + "x": 280, + "y": 2840, "wires": [ [ - "7fa6337cdf0a0bc8" + "cbaa23c34e10fae1" ] ] }, { - "id": "e27d2613e942f344", + "id": "00e7836ccb3c4d0c", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8372,16 +6607,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2740, + "x": 620, + "y": 2840, "wires": [ [] ] }, { - "id": "f02d4a036a225e87", + "id": "7f24c0c34a88ba04", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8389,18 +6624,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2780, + "x": 280, + "y": 2880, "wires": [ [ - "9d464b2ba1edaf48" + "f455fb39039617ae" ] ] }, { - "id": "b7d3fe0c0b40b3e1", + "id": "3019576de193d9d6", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8408,16 +6643,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2780, + "x": 620, + "y": 2880, "wires": [ [] ] }, { - "id": "612cccacda1a65aa", + "id": "77bb7dc529d63a7e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8425,18 +6660,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2900, + "x": 270, + "y": 3000, "wires": [ [ - "282681e7c4351f74" + "e89f61dbe6a6cffe" ] ] }, { - "id": "b17e82651407d8e0", + "id": "885bc559fafec5f2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8444,16 +6679,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 2900, + "x": 590, + "y": 3000, "wires": [ [] ] }, { - "id": "3b126549c03a872e", + "id": "cc6dabe017a9c8a8", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8461,18 +6696,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3220, + "x": 270, + "y": 3320, "wires": [ [ - "661614f5bd2c71d6" + "eef25405472acfee" ] ] }, { - "id": "2af447a6905b83bc", + "id": "12d20f2274bcc511", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8480,16 +6715,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3220, + "x": 590, + "y": 3320, "wires": [ [] ] }, { - "id": "954db931f87894ee", + "id": "dcb9fed8122759fd", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8497,18 +6732,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2940, + "x": 270, + "y": 3040, "wires": [ [ - "ef70d61678fe1f11" + "70014da0b6ab6698" ] ] }, { - "id": "2c812acffdb330c5", + "id": "f70321c96bf81360", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8516,16 +6751,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 2940, + "x": 590, + "y": 3040, "wires": [ [] ] }, { - "id": "6682c8057e89d087", + "id": "013d2057c2347a62", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8533,18 +6768,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2980, + "x": 270, + "y": 3080, "wires": [ [ - "24929b4629f22070" + "2544963852c6881a" ] ] }, { - "id": "ae0654af69446942", + "id": "95e1603bbd06a69d", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8552,16 +6787,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 2980, + "x": 590, + "y": 3080, "wires": [ [] ] }, { - "id": "015be401d08047d2", + "id": "f88bbf11d5aa9a14", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8569,18 +6804,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3020, + "x": 270, + "y": 3120, "wires": [ [ - "8c396b060f3d2646" + "a1394401246eb735" ] ] }, { - "id": "58cf48cfacc979fb", + "id": "a8f92ea6bf394640", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8588,16 +6823,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3020, + "x": 590, + "y": 3120, "wires": [ [] ] }, { - "id": "1c6c0f8b9ac95659", + "id": "301af70731e096e5", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8605,18 +6840,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3060, + "x": 270, + "y": 3160, "wires": [ [ - "a3c58ea48c388215" + "f15ca4518b5f223e" ] ] }, { - "id": "c7ae206f2fff6810", + "id": "06397bb46b3bb541", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8624,16 +6859,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3060, + "x": 590, + "y": 3160, "wires": [ [] ] }, { - "id": "dcee66c0d56c6934", + "id": "0456a9ec4c236c9e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8641,18 +6876,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3100, + "x": 270, + "y": 3200, "wires": [ [ - "9b5da90eaf6ac562" + "49900bb9047dd965" ] ] }, { - "id": "cfebd4a47a68b319", + "id": "687dcdc1ede11700", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8660,16 +6895,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3100, + "x": 590, + "y": 3200, "wires": [ [] ] }, { - "id": "6ec7d85bb17eb159", + "id": "09d37ba08ec0f163", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8677,18 +6912,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3140, + "x": 270, + "y": 3240, "wires": [ [ - "f24cb404d7d09f8a" + "5a90224dc998b417" ] ] }, { - "id": "4f42d02a3776a006", + "id": "37d954a4cf7e87ea", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8696,18 +6931,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3180, + "x": 270, + "y": 3280, "wires": [ [ - "1f79467df98ce894" + "d2364ab09627fe94" ] ] }, { - "id": "90f4d220928e4727", + "id": "e220740c0d38ccb0", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8715,16 +6950,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3140, + "x": 590, + "y": 3240, "wires": [ [] ] }, { - "id": "b05e1e612887f9c2", + "id": "79d7e5a705ab813a", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8732,16 +6967,16 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3180, + "x": 590, + "y": 3280, "wires": [ [] ] }, { - "id": "58bbe9fc41e0d7b9", + "id": "21dc963d967d9c99", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -8749,18 +6984,18 @@ "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3260, + "x": 270, + "y": 3360, "wires": [ [ - "e23a396162026618" + "74e455136b5ca5dd" ] ] }, { - "id": "787a128f84f747c0", + "id": "a4a89668ce4c9f05", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -8768,54 +7003,54 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3260, + "x": 590, + "y": 3360, "wires": [ [] ] }, { - "id": "78351089ee9ebeaf", + "id": "22ef66b0e2058be2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 270, - "y": 340, + "y": 360, "wires": [ [ - "40dee936a9abac0d" + "cb3437ec113e1b6f" ] ] }, { - "id": "5fba78ae65eaaf5d", + "id": "9ce01c8ba97932c1", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 270, - "y": 380, + "y": 400, "wires": [ [ - "4fd9bb53fdb51a25" + "60fd0adce1cfeb82" ] ] }, { - "id": "67206663b3881868", + "id": "81356177176eebcf", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadB", "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, @@ -8824,308 +7059,540 @@ "finalize": "", "libs": [], "x": 270, - "y": 660, + "y": 480, "wires": [ [ - "c833f6243a059d83" + "f6d6cc35679ede63" ] ] }, { - "id": "3492754252645e62", + "id": "b78346ca3ce70c68", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'camera'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 270, - "y": 420, + "y": 320, "wires": [ [ - "a2c1dba3e67be015", - "6f3d403e157163e4" + "f0d8dbcca76a1926" ] ] }, { - "id": "d16525a31223bc42", + "id": "e95b86cbac1b03b9", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'model'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 620, + "x": 550, + "y": 320, "wires": [ - [ - "80b579a4220e5c23", - "c6138801b30f091d" - ] + [] ] }, { - "id": "f99ec8781a33ec7d", + "id": "3e4c15d7b538f816", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", - "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 300, + "x": 670, + "y": 1540, "wires": [ [ - "7dc39bd847d16ded" + "3bf622f344172721" ] ] }, { - "id": "5f849178998d9082", + "id": "0f0871baf322b6d0", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "if(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 550, - "y": 300, + "x": 290, + "y": 1820, "wires": [ - [] + [ + "6ebd15c61a5ca891" + ] ] }, { - "id": "725fd0cab0bddc0e", + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 250, - "y": 940, + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, "wires": [ [ - "49259adad52fc214" + "acd10a4c99ee8063" ] ] }, { - "id": "49259adad52fc214", - "type": "ui_text_input", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Hostname", + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", "tooltip": "", - "group": "0fe66c9190b8a87c", - "order": 6, - "width": 6, + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, "height": 1, "passthru": false, - "mode": "text", - "delay": "0", - "topic": "Change hostname to:", - "sendOnBlur": true, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", "topicType": "str", - "x": 530, + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, "y": 940, "wires": [ [ - "8001f7c361de7d8c" + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" ] ] }, { - "id": "51521bc6eb44cde5", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 980, + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, "wires": [ [ - "59c9f67283ba1709" + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" ] ] }, { - "id": "2bb52656f9554dab", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "ssid = msg.payload\nmsg.topic = 'Add wifi network (' + ssid + ')'\nmsg.payload = 'Enter Wifi password:'\nmsg.ssid = ssid\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 650, - "y": 980, + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, "wires": [ [ - "ebcc98685059b9d4" + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" ] ] }, { - "id": "ebce67b739d1891f", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "chk/change hostname", - "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n pass\n\nwith open('/etc/hostname', 'r') as file:\n old_hostname = file.read().replace('\\n','')\n\nhostname = msg['hostname']\nif len(hostname) < 4 :\n msg['payload'] = ' '\n msg['topic'] = 'ERROR - Hostname NOT changed'\n return msg\n \n\nwith open('/etc/hostname', 'w+') as file:\n file.write(hostname)\nos.system('echo ' + hostname + ' | tee /etc/hostname')\nwith open('/etc/hosts', 'r') as file:\n temp = file.read()\ntemp = temp.replace(old_hostname,hostname)\nwith open('/etc/hosts', 'w') as file:\n file.write(temp)\nos.system('hostnamectl set-hostname ' + hostname)\nos.system('systemctl restart avahi-daemon')\nsave('hostname',hostname)\nmsg['payload'] = hostname\nmsg['topic'] = 'Success - Hostname changed'\nreturn msg\n", - "outputs": 1, - "x": 1140, - "y": 940, + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, "wires": [ [ - "03732a7d3b0c95aa" + "53e6681d7254d484" ] ] }, { - "id": "667ac2aba819f506", + "id": "53e6681d7254d484", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", "sendall": true, "outputs": 1, - "ok": "OK", - "cancel": "Cancel", + "ok": "No", + "cancel": "Yes", "raw": false, "className": "", "topic": "", - "name": "Confirm", - "x": 920, - "y": 940, - "wires": [ - [ - "ebce67b739d1891f" - ] - ] - }, - { - "id": "8001f7c361de7d8c", - "type": "change", - "z": "017bd4e4a428bee5", "name": "", - "rules": [ - { - "t": "set", - "p": "hostname", - "pt": "msg", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 710, - "y": 940, - "wires": [ - [ - "667ac2aba819f506" - ] - ] - }, - { - "id": "9bb0adbd716ce347", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "reboot", - "links": [ - "16c76929f88df841", - "fe3a855fee9e28c6" - ], - "x": 155, - "y": 720, + "x": 270, + "y": 620, "wires": [ [ - "d114f4d4d7f31981", - "cc3cb10f2ea3f8b8" + "c11e79cfa7bc10b7" ] ] }, { - "id": "f9efcb87b74abbd4", + "id": "c11e79cfa7bc10b7", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", - "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 670, - "y": 1520, + "x": 410, + "y": 620, "wires": [ [ - "510dbe4d76253bd6" + "307782d10c1acdaf" ] ] }, { - "id": "adc206aa8edd1e41", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "OSC", - "group": "db43d646.2074c8", - "order": 2, - "width": 3, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, "className": "", - "icon": "fa-question-circle", - "payload": "

Files&Cloud

Refresh

You can refresh the status of the processing of your files in the OpenScanCloud. Make sure to read and agree the terms of use (in settings menu) before using the OpenScanCloud. Do not spam this button, as this might lead to temporary/permanent suspension of your IP address.

The status (in the table) of the individual sets in the file list will be updated to one of the following:

Created - you started the upload of your image set. If you are stuck on this status, please try to restart the upload.

Initialized - all files have been uploaded and processing will start as soon as possible

File approved - the server received and verified your files

Processing started - your files are currently being processed

Processing failed - there are various reasons why processing might fail. Please check the email for more details or contact me at cloud@openscan.eu

processing done - check your email, where you should find a link to the 3d model :)

Status (on the right column)

Indicates, what the device is currently up to.

Refreshing - updating all image set's status

Uploading - while transferring the image set to the OpenScanCloud servers. If the upload freezes, be patient. If nothing happens, reboot the device and restart the upload.

Project started - when the upload of a set was successful

Zipping - files larger then 200mb have to be split and re-zipped before uploading to the OpenScanCloud, the process might take a while depending on the filesize.

Combining - two sets into one might take up to a minute.

Set

select a set from the file list by clicking on a row in the table

Download

Download the selected set from the OpenScan device to your computer/mobile/tablet

Upload

Upload the selected file to the OpenScanCloud

Combine

In order to combine two sets, select one set. Click the combine button and select the second set. A pop-up will appear, and you can confirm the operation. All images from the two sets will be merged into one set. The original image sets will be deleted!

Delete Set/All

Please keep in mind, that the memory of the SD card is relatively small, and thus you will have to delete individual or all photo sets from time to time.

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 590, - "y": 200, + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, "wires": [ - [ - "f304680180a23479" - ] + [] ] }, { - "id": "45df91cae421e8e1", + "id": "cca3300a8f0daf4d", "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Scan_settings", - "group": "7aaf184330605300", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", "order": 1, "width": 6, "height": 1, @@ -9136,733 +7603,778 @@ "bgcolor": "transparent", "className": "", "icon": "fa-question-circle", - "payload": "

Scan Settings

Current Status

--READY-- - everything is okay and ready to go :)

Routine-preparing - before starting the routine some time might pass depending on the number of photos

Routine-stopping - manually ending the routine by pressing the stop button

Routine-Photo X/Y - Showing the progress of the routine

No Camera Found - please check the camera ribbon cable

Error: XXX - Please contact info@openscan.eu or post an issue on Github.com

Projectname

Each photo set will be saved using the following pattern  YYYY-MM-DD_hh-mm-ss_projectname.zip (e.g. 2022-04-05_12.12.12_toysoldier.zip). Keep your files organized by giving each set a new projectname. If not specified 'default' will be used.

Rotor

Moving the rotor by increments of 5°. Please make sure to start the routine with the camera in the horizontal position.

Turntable

Moving the turntable by increments of 15°.

Ringlight

Use the ring light for shadow-free illumination. It is highly recommended to use the polarizer in order to avoid reflections. Note, that the polarizer will absorb 75% of the light, so you might need to use both ring lights.

Photos

Set the number of photos for the current set. 60-120 photos should be more than enough for most objects. If the reconstruction fails or is very bad with 60 photos, increasing the number of photos will not help!

Shutter

Again: Less is more! If the value is too high, some areas might get overexposed and thus, the software will not be able to recognize the surface feature of the object. Here are some reference values:

- no polarizer: 5-20ms

- mostly white object,  with polarizer + one ringlight: 50-200ms

Crop X/Y

Make sure to use the right object holder to place the object in the middle of the screen. Try to crop as many unnecessary areas as possible. This will greatly lower the file size and resulting transfer and reconstruction times!

Start/Stop

Use the buttons to start/stop the routine

Reboot/Shutdown

In case of an error, try to restart the device. Always use the shutdown button before powering-off the device!

", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 760, - "y": 120, + "x": 750, + "y": 180, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "e9677b85856b5873", + "id": "654bc70a18820828", "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "Reset rfkill", - "func": "from os import system\nif \"Interface doesn't support scanning\" in msg['payload']:\n system('rfkill unblock all')\n system('ifconfig wlan0 up')\n return msg", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, - "x": 390, - "y": 1100, + "x": 660, + "y": 2720, "wires": [ [] ] }, { - "id": "91fe20cb16f54293", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1680, - "wires": [ - [ - "327c8bdde31033a4" - ] - ] - }, - { - "id": "add3e998b097c54f", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 7, - "width": 3, - "height": 1, - "name": "rotor_anglemin", - "label": "Min Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1680, - "wires": [] - }, - { - "id": "da286366433c83a0", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1680, + "x": 660, + "y": 2680, "wires": [ [] ] }, { - "id": "327c8bdde31033a4", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "rotor_anglemin", - "label": "", - "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 8, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", - "className": "", - "x": 440, - "y": 1680, - "wires": [ - [ - "da286366433c83a0" - ] - ] - }, - { - "id": "94288df4c6756197", + "id": "81bd4381cd029958", "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "rotor_anglemax", + "z": "e43a27722b508115", + "name": "cam_delay_after", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 10, + "group": "d324f0b852c2df0a", + "order": 9, "width": 3, "height": 1, "passthru": false, "outs": "end", "topic": "", "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", + "min": "0", + "max": "1", + "step": "0.02", "className": "", "x": 440, - "y": 1720, + "y": 2560, "wires": [ [ - "e531ffe3dcf34eb4" + "e612073aded01a8f" ] ] }, { - "id": "4702a4a09124e27d", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "rotor_anglestart", - "label": "", - "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 6, + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, "width": 3, "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", "className": "", - "x": 440, - "y": 1760, - "wires": [ - [ - "9ce407cb16f0419a" - ] - ] + "x": 760, + "y": 2560, + "wires": [] }, { - "id": "2cf946c7aab2cbb4", + "id": "6281b2e6e081104d", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 1720, + "x": 280, + "y": 2560, "wires": [ [ - "94288df4c6756197" + "81bd4381cd029958" ] ] }, { - "id": "e531ffe3dcf34eb4", + "id": "e612073aded01a8f", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 1720, + "x": 620, + "y": 2560, "wires": [ [] ] }, { - "id": "4da5f650d3845baa", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1760, + "x": 270, + "y": 520, "wires": [ - [ - "4702a4a09124e27d" - ] + [] ] }, { - "id": "9ce407cb16f0419a", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1760, + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, "wires": [ - [] + [ + "e2411b49791840e0" + ] ] }, { - "id": "fda776c5aa642867", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 9, - "width": 3, + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, "height": 1, - "name": "rotor_anglemax", - "label": "Max Angle", - "format": "", - "layout": "row-left", + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, "className": "", - "x": 780, - "y": 1720, - "wires": [] + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] }, { - "id": "6e9af48a1c4c58c6", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", "order": 5, - "width": 3, + "width": 6, "height": 1, - "name": "rotor_anglestart", - "label": "Start Angle", - "format": "", - "layout": "row-left", + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, "className": "", - "x": 780, - "y": 1760, - "wires": [] - }, - { - "id": "9b2bc9849aee310b", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "changeHostname", - "links": [ - "ec2db55a99bbe3ee", - "d5175561293ef490", - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 835, - "y": 900, + "topicType": "str", + "x": 220, + "y": 1020, "wires": [ [ - "8b9e3781511e9231" + "a7d233f984009e2e" ] ] }, { - "id": "8b9e3781511e9231", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "chk", - "func": "with open('/etc/hostname', 'r') as file:\n old_hostname = file.read().replace('\\n','')\nif old_hostname == 'raspberrypi':\n msg['hostname'] = 'openscan'\n msg['payload'] = 'OK'\n return msg", - "outputs": 1, - "x": 930, - "y": 900, + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, "wires": [ [ - "ebce67b739d1891f" + "a7d233f984009e2e" ] ] }, { - "id": "3fcbd9fe3acc3fb7", + "id": "84ecaafd629c0f7a", "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "scan_arducam", - "group": "90223f7ddc082321", - "order": 1, - "width": 2, - "height": 1, + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, "passthru": false, - "label": "", + "label": "Connect to Wifi", "tooltip": "", "color": "", - "bgcolor": "transparent", + "bgcolor": "", "className": "", - "icon": "fa-question-circle", - "payload": "

Focus Settings

MF - Manual Focus

By default, the switch is 'off', which means that autofocus is active. For small objects, it might be necessary to use manual focus: activate the switch and set the focus by pressing + and - accordingly. The distance is measured between the camera lens and the focal plane (which should be in the center or slightly in front of the center of the object). Be aware, that the distance value is only a rough estimate (mm)

ST - Stacking

Stacking is disabled by default. Once activated, you will be able to set the following:

Stacksize - defines the number of photos between the minimal and the maximal focal distance

SET press this button to set the maximal/minimal focal distance. Pressing the button a third time will re-set the values.

", + "icon": "", + "payload": "", "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 760, - "y": 160, + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, "wires": [ [ - "f304680180a23479" + "a7d233f984009e2e" ] ] }, { - "id": "6d68cccec646e0a0", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "enable routine", - "func": "msg_enable = {}\nmsg_disable = {}\n\nmsg_enable['enabled'] = True\nmsg_disable['enabled'] = False\n\nif msg['payload'] == 'external':\n return msg_enable, msg_disable\nif msg['payload'] == 'gphoto':\n return msg_enable, msg_enable, msg_disable\n\nreturn msg_enable", - "outputs": 3, - "x": 560, - "y": 440, + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, "wires": [ [ - "a0ba1aa77c5c8b7c" - ], - [ - "a42c12e94f65fa01" - ], - [ - "2d76e5617f13cd6c" + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" ] ] }, { - "id": "a0ba1aa77c5c8b7c", - "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", - "mode": "link", + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", "links": [ - "2aea1727dbea76ce", - "4f212b44aa487945", - "65cef204b16f8741", - "917a194be245384a" + "200d4b9951b6e066" ], - "x": 675, - "y": 420, - "wires": [] + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] }, { - "id": "a42c12e94f65fa01", - "type": "link out", - "z": "017bd4e4a428bee5", + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", "name": "", - "mode": "link", - "links": [ - "2aea1727dbea76ce", - "4f212b44aa487945", - "65cef204b16f8741", - "917a194be245384a" - ], - "x": 715, - "y": 440, + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, "wires": [] }, { - "id": "2d76e5617f13cd6c", + "id": "c994c779e4bad800", "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", + "z": "e43a27722b508115", + "name": "link out 2", "mode": "link", "links": [ - "65cef204b16f8741" + "592ec13d8f8923a9" ], - "x": 675, - "y": 460, + "x": 815, + "y": 1020, "wires": [] }, { - "id": "bd80ec228fb9a86d", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 135, - "y": 340, + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, "wires": [ [ - "78351089ee9ebeaf", - "5fba78ae65eaaf5d", - "3492754252645e62", - "d16525a31223bc42", - "67206663b3881868" - ] + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] ] }, { - "id": "65b38bfeb3fee710", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } ], - "x": 155, - "y": 760, + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, "wires": [ [ - "cc3cb10f2ea3f8b8" + "1eef47e0074545a9" ] ] }, { - "id": "d3fc91d87d5d5f62", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 135, + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, "y": 940, - "wires": [ - [ - "725fd0cab0bddc0e" - ] - ] + "wires": [] }, { - "id": "cc9c4092edeb43cc", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 135, - "y": 1020, + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, "wires": [ [ - "27c6b221c90ed9e1", - "f393400.d87dcc" + "85ad07b8f973bbe2" ] ] }, { - "id": "f0b355967b33dfee", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 175, - "y": 1600, - "wires": [ - [ - "91fe20cb16f54293", - "2cf946c7aab2cbb4", - "4da5f650d3845baa", - "6659121906897a1f", - "15f02421b30a9ab6", - "58928befcc61b1f7", - "569829eeff715c33", - "c997e60519341afd", - "59ecf3a22cd3a669", - "27bc56f273360ac7", - "9b89eb1eaf333c10", - "2e8927be0e235fa1", - "f46ced86106306c8", - "4339704cd8552eb3", - "1ac53bb6150645fe", - "0d48bb415c584420", - "b6e420121e6466e7" - ] - ] + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] }, { - "id": "d7c1fb4c028b21a5", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 155, - "y": 2280, + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, "wires": [ - [ - "d5308090f2b7971a", - "694d1068bea15171", - "cec3e5e78a40476b", - "6f524f9370a18482", - "1f87f473e327c3cc", - "cff7ac5f1e061855", - "cf854461c37ca54f", - "ba10e04dd1761692", - "a69d216114f908a5", - "f02d4a036a225e87", - "1efd4a05aee0b86c", - "6841e5a392f0fb4f" - ] + [] ] }, { - "id": "a67c18aaca2f5fa5", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 155, - "y": 2900, + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, "wires": [ [ - "612cccacda1a65aa", - "954db931f87894ee", - "6682c8057e89d087", - "015be401d08047d2", - "1c6c0f8b9ac95659", - "dcee66c0d56c6934", - "6ec7d85bb17eb159", - "4f42d02a3776a006", - "3b126549c03a872e", - "58bbe9fc41e0d7b9" + "6b7245c3dcb694c8" ] ] }, { - "id": "c6d3821bc7f43f8e", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Reset default", - "group": "4fe6b4c0ade0938a", - "order": 14, - "width": 6, - "height": 1, - "passthru": false, - "label": "Restore default settings", + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", "tooltip": "", - "color": "red", - "bgcolor": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, "className": "", - "icon": "", - "payload": "This can not be undone!", - "payloadType": "str", - "topic": "Restore default settings?", - "topicType": "str", - "x": 930, - "y": 300, + "x": 460, + "y": 1940, "wires": [ [ - "e4be21c38b57f560" + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" ] ] }, { - "id": "e4be21c38b57f560", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1090, - "y": 300, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, "wires": [ [ - "9f30de04ced693d3" + "253feafa5a2f8b1d" ] ] }, { - "id": "9f30de04ced693d3", + "id": "1916dc3fd04f0664", "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 1230, - "y": 300, + "x": 630, + "y": 1940, "wires": [ - [ - "80bccc884b0be297" - ] + [] ] }, { - "id": "80bccc884b0be297", - "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 1325, - "y": 300, + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, "wires": [] }, { - "id": "34b685aff2080d31", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "boot-cam", - "func": "from OpenScan import load_str\n\ncamera_modules = ('imx519', 'imx219', 'ov5647', 'imx477', 'imx378', 'ov9281', 'imx290a', 'imx290b')\n\npt1 = \"[all]\\n\\ncamera_auto_detect=0\\ngpu_mem=256\\ndtoverlay=vc4-fkms-v3d\\ndtoverlay=\"\npt3 = \",media-controller=1\\n\"\n\nwith open('/boot/config.txt', 'r') as file:\n config = file.read()\n\ncamera = load_str('camera')\nif camera not in camera_modules:\n msg['payload'] = 'no changes'\n return\n\nif camera == 'imx290a':\n camera = 'imx290,clock-frequency=37125000'\nelif camera == 'imx290b':\n camera = 'imx290,clock-frequency=74250000'\n\nconfig_keep = config.split('[all]\\n')[0]\nconfig_new = config_keep + pt1 + camera + pt3\n\nwith open('/boot/config.txt', 'w') as file:\n file.write(config_new)\n\nmsg['topic'] = 'Camera configuration changed'\nmsg['payload'] = 'Please restart the device'\n\nreturn msg", + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", "outputs": 1, - "x": 680, - "y": 500, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, "wires": [ [ - "68cba0c530c6def6" + "69516440e3997111", + "f036424d79645761" ] ] }, { - "id": "68cba0c530c6def6", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 830, - "y": 500, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, "wires": [ - [] + [ + "eefed04c25e3e4d6" + ] ] }, { - "id": "f304680180a23479", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": true, - "className": "", - "topic": "", - "name": "Info", - "x": 1010, - "y": 120, + "x": 660, + "y": 440, "wires": [ [] ] }, { - "id": "0d48bb415c584420", + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'turntable_mode'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 1640, + "x": 190, + "y": 3560, "wires": [ [ - "ce215e159ce7267f" + "d0a1a4947a1137ca" ] ] }, { - "id": "ce215e159ce7267f", + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", "type": "ui_switch", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Turntable Mode", - "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 2, - "width": 6, - "height": 1, + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", "passthru": true, "decouple": "false", - "topic": "", - "topicType": "str", + "topic": "topic", + "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", @@ -9874,179 +8386,286 @@ "offcolor": "", "animate": false, "className": "", - "x": 440, - "y": 1640, + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, "wires": [ [ - "f95f528dec31425c" + "1c08a329bd2a669c" ] ] }, { - "id": "f95f528dec31425c", + "id": "bf8e971a52cddab1", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'turntable_mode'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 1640, + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, "wires": [ [] ] }, { - "id": "4ebe5baece5ce9f2", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "preview_resolution", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", "order": 5, - "width": 3, + "width": 6, "height": 1, "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.5", - "max": "10", - "step": "0.5", + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, "className": "", - "x": 450, - "y": 2280, + "topicType": "msg", + "x": 350, + "y": 3640, "wires": [ [ - "60a415fff23cb55e" + "b5aba11033c5f952" ] ] }, { - "id": "9ed0498cceceedde", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 4, - "width": 3, - "height": 1, - "name": "preview_res", - "label": "Preview Resolution (Mpx)", - "format": "", - "layout": "row-spread", + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, +{ + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", "x": 770, - "y": 2280, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, +{ + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, +{ + "id": "69885a9ce218eb71", + "type": "comment", + "z": "e43a27722b508115", + "name": "Coloritos", + "info": "", + "x": 100, + "y": 3740, "wires": [] }, { - "id": "1efd4a05aee0b86c", + "id": "dc1cde67c3022e6b", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'cam_preview_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data)/1000000;\nreturn msg", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2280, + "x": 190, + "y": 3800, "wires": [ [ - "4ebe5baece5ce9f2" + "0dccca85770c7936" ] ] }, { - "id": "60a415fff23cb55e", + "id": "b63e8246ad14ad9d", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_preview_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload*1000000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "interface-color", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2280, + "x": 540, + "y": 3800, "wires": [ [] ] }, { - "id": "6f3d403e157163e4", + "id": "b7044aa75196b521", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3800, + "wires": [ + [ + "dc1cde67c3022e6b" + ] + ] + }, + { + "id": "0dccca85770c7936", "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "Camera", + "z": "e43a27722b508115", + "name": "interface_color", "label": "", "tooltip": "", "place": "Select option", - "group": "1f7f7e1e24f5ad9b", - "order": 5, - "width": 4, - "height": 1, + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, "passthru": true, "multiple": false, "options": [ { - "label": "Pi Cam v1 - 5mp", - "value": "ov5647", - "type": "str" - }, - { - "label": "Pi Cam v2 - 8mp", - "value": "imx219", - "type": "str" - }, - { - "label": "Pi Cam HQ - 12.3mp", - "value": "imx477", - "type": "str" - }, - { - "label": "Arducam IMX519 - 16mp", - "value": "imx519", - "type": "str" - }, - { - "label": "IMX290 a", - "value": "imx290a", - "type": "str" - }, - { - "label": "IMX290 b", - "value": "imx290b", - "type": "str" - }, - { - "label": "IMX378", - "value": "imx378", + "label": "Aburrido", + "value": "#097479", "type": "str" }, { - "label": "OV9281", - "value": "ov9281", + "label": "Morado", + "value": "#790974", "type": "str" }, { - "label": "DSLR (gphoto)", - "value": "gphoto", + "label": "Berenjena", + "value": "#79093c", "type": "str" }, { - "label": "USB Webcam", - "value": "usb_webcam", + "label": "Azul", + "value": "#093c79 ", "type": "str" }, { - "label": "External Camera", - "value": "external", + "label": "Oliva", + "value": "#747909", "type": "str" } ], @@ -10054,154 +8673,133 @@ "topic": "topic", "topicType": "msg", "className": "", - "x": 400, - "y": 460, + "x": 360, + "y": 3800, "wires": [ [ - "6d68cccec646e0a0", - "4058a31e942e8f95" + "b63e8246ad14ad9d" ] ] }, - { - "id": "c6138801b30f091d", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "model", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "1f7f7e1e24f5ad9b", - "order": 3, - "width": 4, - "height": 1, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Please Select", - "value": "None", - "type": "str" - }, - { - "label": "OpenScan Mini", - "value": "OSMini", - "type": "str" - }, - { - "label": "OpenScan Classic", - "value": "OSClassic", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 390, - "y": 580, +{ + "id": "667950f6671bd1a0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 840, "wires": [ [ - "896242c5a7e50fa7" + "b82a1cbefad51cd8" ] ] }, { - "id": "4da67c23c7a543a0", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "1f7f7e1e24f5ad9b", - "order": 4, - "width": 2, - "height": 1, - "name": "", - "label": "Camera", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 840, - "y": 460, - "wires": [] - }, - { - "id": "1fed8676078ea9a7", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "1f7f7e1e24f5ad9b", - "order": 2, - "width": 2, - "height": 1, - "name": "", - "label": "Model", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 580, - "wires": [] - }, - { - "id": "a4b7eea9a9736b0a", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Update&Info", - "group": "ddbd496e.93a288", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", + "id": "5f32d7e78e368454", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "b82a1cbefad51cd8", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "hostname", + "label": "Hostname", "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", - "payloadType": "str", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "mode": "text", + "delay": 300, "topic": "topic", + "sendOnBlur": true, + "className": "", "topicType": "msg", - "x": 750, - "y": 200, + "x": 360, + "y": 840, "wires": [ [ - "f304680180a23479" + "5f32d7e78e368454" ] ] }, +{ + "id": "5fd155711e29b1b8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Monitoring", + "info": "", + "x": 100, + "y": 3860, + "wires": [] + }, { - "id": "b6e420121e6466e7", + "id": "815702499384f118", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'routine_secondpass'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 1600, + "x": 190, + "y": 3920, "wires": [ [ - "ab8d5cfe9190bb5f" + "bfdbdae28bf42ed4" ] ] }, { - "id": "ab8d5cfe9190bb5f", + "id": "464c8495f86daaa7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "datadog_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3920, + "wires": [ + [] + ] + }, + { + "id": "bfdbdae28bf42ed4", "type": "ui_switch", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Second pass", - "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 3, - "width": 6, - "height": 1, + "z": "e43a27722b508115", + "name": "datadog_enable", + "label": "Enable Datadog", + "tooltip": "Enable Datadog monitoring", + "group": "33aff36289823faa", + "order": 1, + "width": "6", + "height": "1", "passthru": true, "decouple": "false", - "topic": "", - "topicType": "str", + "topic": "topic", + "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", @@ -10213,206 +8811,261 @@ "offcolor": "", "animate": false, "className": "", - "x": 430, - "y": 1600, + "x": 340, + "y": 3920, + "wires": [ + [ + "464c8495f86daaa7" + ] + ] + }, + { + "id": "f93ce2d26953341f", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "label": "Datadog Api Token", + "tooltip": "Datadog Api Token", + "group": "33aff36289823faa", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3960, "wires": [ [ - "fa51327f0140b045" + "647641e79884eb87" ] ] }, { - "id": "fa51327f0140b045", + "id": "ee668e39d213070b", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'routine_secondpass'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 1600, + "x": 190, + "y": 3960, "wires": [ - [] + [ + "f93ce2d26953341f" + ] ] }, { - "id": "6841e5a392f0fb4f", + "id": "647641e79884eb87", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'cam_output_downscale'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2320, + "x": 550, + "y": 3960, + "wires": [ + [] + ] + }, + { + "id": "ff2dea1ab9cb7776", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 4", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3960, "wires": [ [ - "110216d678fad14f" + "815702499384f118", + "ee668e39d213070b" ] ] }, - { - "id": "110216d678fad14f", - "type": "ui_switch", - "z": "017bd4e4a428bee5", +{ + "id": "a1b81e7fe94ad4e5", + "type": "python3-function", + "z": "e43a27722b508115", "name": "", - "label": "Downscale output", + "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", + "outputs": 1, + "x": 530, + "y": 3740, + "wires": [ + [] + ] + }, + { + "id": "2f3a3c0e682ae862", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "restart_interface", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "Restart Interface", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 6, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, + "color": "", + "bgcolor": "", "className": "", - "x": 450, - "y": 2320, + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 340, + "y": 3740, "wires": [ [ - "214d548d564f8ba2" + "a1b81e7fe94ad4e5" ] ] }, { - "id": "214d548d564f8ba2", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_output_downscale'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n \nmsg.enabled = msg.payload\nreturn msg", + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2320, + "x": 260, + "y": 140, "wires": [ [ - "1becbff4884b8c1a" + "e23c514008cad1a1" ] ] }, { - "id": "8be1ca844a6caa54", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "output_resolution", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 8, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, "topic": "", - "topicType": "str", - "min": "0.5", - "max": "20", - "step": "0.5", - "className": "", - "x": 450, - "y": 2360, + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, "wires": [ [ - "a6b2c0a0604ccf14" + "4c7fa5b5b27b83a5" ] ] }, { - "id": "9ac09d89d791e953", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 7, - "width": 3, - "height": 1, - "name": "image_res", - "label": "Output Resolution (Mpx)", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2360, - "wires": [] - }, - { - "id": "1becbff4884b8c1a", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'cam_output_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data)/1000000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2360, + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, "wires": [ [ - "8be1ca844a6caa54" + "4ce127c61c3c5966", + "beacc3dc5398fa79" ] ] }, { - "id": "a6b2c0a0604ccf14", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_output_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload*1000000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2360, + "x": 290, + "y": 220, "wires": [ [] ] }, { - "id": "f358de1e64b491bb", + "id": "beacc3dc5398fa79", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ - "b30d918661392ab3", - "44c598049cd533fd" + "38783aea9cc317a6" ], - "x": 635, - "y": 620, + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, "wires": [] }, { "id": "b0629875a30ae1d7", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", "outputs": 2, - "x": 350, - "y": 240, + "x": 390, + "y": 540, "wires": [ [ "1bbe2d769f42c313" @@ -10425,7 +9078,7 @@ { "id": "c7b6d05a62172432", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "ddbd496e.93a288", "order": 3, "width": 0, @@ -10435,19 +9088,19 @@ "format": "{{msg.status}}", "layout": "row-spread", "className": "", - "x": 170, - "y": 100, + "x": 210, + "y": 400, "wires": [] }, { "id": "fefe45404bdb19c4", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, - "x": 510, - "y": 260, + "x": 550, + "y": 560, "wires": [ [ "1bbe2d769f42c313", @@ -10458,14 +9111,14 @@ { "id": "d0104e0163745993", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "links": [ "960912e90ba5b5bc", "50eeb3e362f9027f" ], - "x": 75, - "y": 140, + "x": 115, + "y": 440, "wires": [ [ "ec30638407332e43", @@ -10477,7 +9130,7 @@ { "id": "ec30638407332e43", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "loadS", "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", "outputs": 1, @@ -10485,8 +9138,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 180, + "x": 210, + "y": 480, "wires": [ [ "2852023f3aa8db10" @@ -10496,7 +9149,7 @@ { "id": "2852023f3aa8db10", "type": "ui_dropdown", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "label": "", "tooltip": "", @@ -10510,21 +9163,26 @@ "options": [ { "label": "stable", - "value": "main", + "value": "stable", "type": "str" }, { "label": "beta", "value": "beta", "type": "str" - } + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", - "x": 300, - "y": 180, + "x": 340, + "y": 480, "wires": [ [ "1e10b387ee30c486" @@ -10534,7 +9192,7 @@ { "id": "1e10b387ee30c486", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "write", "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -10542,8 +9200,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 430, - "y": 180, + "x": 470, + "y": 480, "wires": [ [] ] @@ -10551,7 +9209,7 @@ { "id": "274129c51b0b87ef", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "ddbd496e.93a288", "order": 4, "width": 4, @@ -10561,14 +9219,14 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 570, - "y": 180, + "x": 610, + "y": 480, "wires": [] }, { "id": "51cd8c8643e6b46a", "type": "ui_switch", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "label": "Auto-check update availability", "tooltip": "", @@ -10591,8 +9249,8 @@ "offcolor": "", "animate": false, "className": "", - "x": 370, - "y": 140, + "x": 410, + "y": 440, "wires": [ [ "1ab4c6b4b232a022" @@ -10602,7 +9260,7 @@ { "id": "38cbf7965d1c1834", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "loadB", "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, @@ -10610,8 +9268,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 140, + "x": 210, + "y": 440, "wires": [ [ "51cd8c8643e6b46a" @@ -10621,7 +9279,7 @@ { "id": "1ab4c6b4b232a022", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "write", "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -10629,8 +9287,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 570, - "y": 140, + "x": 610, + "y": 440, "wires": [ [] ] @@ -10638,7 +9296,7 @@ { "id": "ae92a328af306ebb", "type": "ui_toast", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "position": "dialog", "displayTime": "3", "highlight": "", @@ -10650,8 +9308,8 @@ "className": "", "topic": "", "name": "", - "x": 670, - "y": 260, + "x": 710, + "y": 560, "wires": [ [ "2de63e8e3ae5fb0c", @@ -10662,14 +9320,14 @@ { "id": "cbd0afc4aa7b302a", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "update status", "links": [ "1bbe2d769f42c313", "42061b28cff81f99" ], - "x": 75, - "y": 100, + "x": 115, + "y": 400, "wires": [ [ "c7b6d05a62172432", @@ -10680,20 +9338,20 @@ { "id": "1bbe2d769f42c313", "type": "link out", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ "cbd0afc4aa7b302a" ], - "x": 625, - "y": 220, + "x": 665, + "y": 520, "wires": [] }, { "id": "7cf60615d93e696b", "type": "ui_button", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", "order": 7, @@ -10710,8 +9368,8 @@ "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 140, - "y": 260, + "x": 180, + "y": 560, "wires": [ [ "b0629875a30ae1d7" @@ -10721,12 +9379,12 @@ { "id": "2de63e8e3ae5fb0c", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, - "x": 840, - "y": 260, + "x": 880, + "y": 560, "wires": [ [ "42061b28cff81f99", @@ -10737,7 +9395,7 @@ { "id": "929281fef53e09f8", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "msg", "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", "outputs": 1, @@ -10745,8 +9403,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 810, - "y": 220, + "x": 850, + "y": 520, "wires": [ [ "42061b28cff81f99" @@ -10756,20 +9414,20 @@ { "id": "42061b28cff81f99", "type": "link out", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ "cbd0afc4aa7b302a" ], - "x": 955, - "y": 220, + "x": 995, + "y": 520, "wires": [] }, { "id": "49f1ecb29a3f84f4", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "loadB", "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", "outputs": 1, @@ -10777,8 +9435,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 220, + "x": 210, + "y": 520, "wires": [ [ "b0629875a30ae1d7" @@ -10788,27 +9446,28 @@ { "id": "fe3a855fee9e28c6", "type": "link out", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ - "9bb0adbd716ce347" + "9bb0adbd716ce347", + "01c882fcc51b349c" ], - "x": 955, - "y": 260, + "x": 995, + "y": 560, "wires": [] }, { "id": "5e7d5e4335d37794", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "links": [ "960912e90ba5b5bc", "50eeb3e362f9027f" ], - "x": 55, - "y": 400, + "x": 95, + "y": 700, "wires": [ [ "2bb5fe78e09fec8a" @@ -10818,12 +9477,12 @@ { "id": "2bb5fe78e09fec8a", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "msg", "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", "outputs": 1, - "x": 170, - "y": 400, + "x": 210, + "y": 700, "wires": [ [ "dbc77052ac950624", @@ -10841,7 +9500,7 @@ { "id": "d97c3068ef5fef96", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 2, "width": 0, @@ -10851,14 +9510,14 @@ "format": "{{msg.os}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 440, + "x": 490, + "y": 740, "wires": [] }, { "id": "73a3b828f862312b", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 8, "width": 0, @@ -10868,14 +9527,14 @@ "format": "{{msg.flask}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 480, + "x": 490, + "y": 780, "wires": [] }, { "id": "dbc77052ac950624", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 1, "width": 0, @@ -10885,59 +9544,17 @@ "format": "{{msg.device}}", "layout": "row-spread", "className": "", - "x": 460, - "y": 400, + "x": 500, + "y": 700, "wires": [] }, - { - "id": "4c7fa5b5b27b83a5", - "type": "python3-function", - "z": "c8e7ecb5849edb9a", - "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'beta'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\ndel msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/Arducam.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/Arducam.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/OpenScan.py'\nmsg[scope]['3']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/config.txt'\nmsg[scope]['4']['dst'] = '/boot/config.txt'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/flows.json'\nmsg[scope]['5']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['6'] = {}\nmsg[scope]['6']['src'] = scope + '/settings.js'\nmsg[scope]['6']['dst'] = '/root/.node-red/settings.js'\n\nmsg[scope]['7'] = {}\nmsg[scope]['7']['src'] = 'files/logo.jpg'\nmsg[scope]['7']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", - "outputs": 1, - "x": 300, - "y": 820, - "wires": [ - [] - ] - }, - { - "id": "80175eb8dc6ad009", - "type": "inject", - "z": "c8e7ecb5849edb9a", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 140, - "y": 820, - "wires": [ - [ - "4c7fa5b5b27b83a5" - ] - ] - }, { "id": "3f42560297fe6978", "type": "ui_template", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "name": "Download LOG", - "order": 9, + "order": 10, "width": 6, "height": 1, "format": "\n
Download error log\n
\n", @@ -10946,8 +9563,8 @@ "resendOnRefresh": false, "templateScope": "local", "className": "", - "x": 140, - "y": 760, + "x": 180, + "y": 1060, "wires": [ [] ] @@ -10955,12 +9572,12 @@ { "id": "c94623ddd9d95f78", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "get update", "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", "outputs": 1, - "x": 170, - "y": 60, + "x": 210, + "y": 360, "wires": [ [] ] @@ -10968,13 +9585,13 @@ { "id": "39a502b38837273d", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "links": [ "1e7457ea9c2c5e09" ], - "x": 205, - "y": 300, + "x": 245, + "y": 600, "wires": [ [ "b0629875a30ae1d7" @@ -10984,7 +9601,7 @@ { "id": "901e31453b2bdff8", "type": "delay", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "pauseType": "delay", "timeout": "10", @@ -10998,8 +9615,8 @@ "drop": false, "allowrate": false, "outputs": 1, - "x": 180, - "y": 440, + "x": 220, + "y": 740, "wires": [ [ "2bb5fe78e09fec8a" @@ -11009,7 +9626,7 @@ { "id": "f983854748ee4763", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 3, "width": 0, @@ -11019,14 +9636,14 @@ "format": "{{msg.osdate}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 520, + "x": 490, + "y": 820, "wires": [] }, { "id": "5347c7c517f5e8c7", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 4, "width": 0, @@ -11036,14 +9653,14 @@ "format": "{{msg.temp}}", "layout": "row-spread", "className": "", - "x": 470, - "y": 560, + "x": 510, + "y": 860, "wires": [] }, { "id": "3a5016f7003cd72c", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 5, "width": 0, @@ -11053,14 +9670,14 @@ "format": "{{msg.cpu}}", "layout": "row-spread", "className": "", - "x": 480, - "y": 600, + "x": 520, + "y": 900, "wires": [] }, { "id": "6d720c4a4ecd9475", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 6, "width": 0, @@ -11070,14 +9687,14 @@ "format": "{{msg.swap}}", "layout": "row-spread", "className": "", - "x": 480, - "y": 640, + "x": 520, + "y": 940, "wires": [] }, { "id": "6438b7d060a70d81", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 7, "width": 0, @@ -11087,70 +9704,14 @@ "format": "{{msg.diskspace}}", "layout": "row-spread", "className": "", - "x": 470, - "y": 680, - "wires": [] - }, - { - "id": "d7362e6e0ec7bdaa", - "type": "inject", - "z": "c8e7ecb5849edb9a", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 130, - "y": 900, - "wires": [ - [ - "4ce127c61c3c5966", - "beacc3dc5398fa79" - ] - ] - }, - { - "id": "4ce127c61c3c5966", - "type": "python3-function", - "z": "c8e7ecb5849edb9a", - "name": "prepare image creation", - "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", - "outputs": 1, - "x": 330, - "y": 900, - "wires": [ - [] - ] - }, - { - "id": "beacc3dc5398fa79", - "type": "link out", - "z": "c8e7ecb5849edb9a", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 235, - "y": 940, + "x": 510, + "y": 980, "wires": [] }, { "id": "8d012912f302be85", "type": "ui_button", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", "order": 8, @@ -11167,8 +9728,8 @@ "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 170, - "y": 340, + "x": 210, + "y": 640, "wires": [ [ "5242607a723cc628" @@ -11178,12 +9739,12 @@ { "id": "5242607a723cc628", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "Changelog", - "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/OpenScan-org/OpenScan-Doc/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", "outputs": 1, - "x": 390, - "y": 340, + "x": 430, + "y": 640, "wires": [ [ "573722197b15bf84" @@ -11193,7 +9754,7 @@ { "id": "573722197b15bf84", "type": "ui_toast", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "position": "dialog", "displayTime": "3", "highlight": "", @@ -11205,8 +9766,49 @@ "className": "", "topic": "", "name": "", - "x": 570, - "y": 340, + "x": 610, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "cde61b7de9eeaba7", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "3ce32450.e0cffc", + "order": 9, + "width": 0, + "height": 0, + "passthru": false, + "label": "Expand Root", + "tooltip": "Sets the maximum space your SD card admits", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "expand", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 510, + "y": 1020, + "wires": [ + [ + "eab36487d201f867" + ] + ] + }, + { + "id": "eab36487d201f867", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "", + "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", + "outputs": 1, + "x": 690, + "y": 1020, "wires": [ [] ] diff --git a/update/beta/settings.js b/update/beta/settings.js index 834f457..357b02b 100644 --- a/update/beta/settings.js +++ b/update/beta/settings.js @@ -1,5 +1,5 @@ /** - * Node-RED Settings created at Mon, 24 Jan 2022 08:17:31 GMT + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT * * It can contain any valid JavaScript code that will get run when Node-RED * is started. @@ -19,6 +19,7 @@ * - Node Settings * **/ +process.env.HOSTNAME = require('os').hostname(); module.exports = { @@ -54,7 +55,8 @@ module.exports = { * property can be used */ //userDir: '/home/nol/.node-red/', - userDir: '/home/pi/OpenScan/settings/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + /** Node-RED scans the `nodes` directory in the userDir to find local node files. * The following property can be used to specify an additional directory to scan. */ @@ -137,11 +139,12 @@ module.exports = { * - httpNodeCors * - httpNodeMiddleware * - httpStatic + * - httpStaticRoot ******************************************************************************/ /** the tcp port that the Node-RED web server is listening on */ -// uiPort: process.env.PORT || 1880, -uiPort: process.env.PORT || 80, + uiPort: process.env.PORT || 80, + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. * To listen on all IPv6 addresses, set uiHost to "::", * The following property can be used to listen on a specific interface. For @@ -164,8 +167,8 @@ uiPort: process.env.PORT || 80, * The following property can be used to specify a different root path. * If set to false, this is disabled. */ - //httpAdminRoot: '/admin', -httpAdminRoot: '/editor', + httpAdminRoot: '/editor', + /** The following property can be used to add a custom middleware function * in front of all admin http routes. For example, to set custom http * headers. It can be a single function or an array of middleware functions. @@ -218,9 +221,28 @@ httpAdminRoot: '/editor', /** When httpAdminRoot is used to move the UI to a different root path, the * following property can be used to identify a directory of static content * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot */ - //httpStatic: '/home/nol/node-red-static/', -httpStatic: '/home/pi/OpenScan/', + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + /******************************************************************************* * Runtime Settings * - lang @@ -348,9 +370,9 @@ httpStatic: '/home/pi/OpenScan/', }, codeEditor: { /** Select the text editor component used by the editor. - * Defaults to "ace", but can be set to "ace" or "monaco" + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired */ - lib: "ace", + lib: "monaco", options: { /** The follow options only apply if the editor is set to "monaco" * @@ -360,7 +382,7 @@ httpStatic: '/home/pi/OpenScan/', */ theme: "vs", /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. - * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html */ //fontSize: 14, //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", @@ -405,13 +427,14 @@ httpStatic: '/home/pi/OpenScan/', * will allow the `os` module to be accessed in a Function node using: * global.get("os") */ - functionGlobalContext: { - os:require('os'), - path:require('path'), - fs:require('fs'), - -}, - +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, /** The maximum number of messages nodes will buffer internally as part of their * operation. This applies across a range of nodes that operate on message sequences. * defaults to no limit. A value of 0 also means no limit is applied. @@ -425,8 +448,8 @@ httpStatic: '/home/pi/OpenScan/', * middleware:{function or array}, (req,res,next) - http middleware * ioMiddleware:{function or array}, (socket,next) - socket.io middleware */ - //ui: { path: "ui" }, -ui: { path: "" }, + ui: { path: "" }, + /** Colourise the console output of the debug node */ //debugUseColors: true, diff --git a/update/betaArdu/Arducam.py b/update/betaArdu/Arducam.py deleted file mode 100644 index 941e07b..0000000 --- a/update/betaArdu/Arducam.py +++ /dev/null @@ -1,202 +0,0 @@ -import time -import os - -try: - import v4l2 -except Exception as e: - print(e) - print("Try to install v4l2-fix") - try: - from pip import main as pipmain - except ImportError: - from pip._internal import main as pipmain - pipmain(['install', 'v4l2-fix']) - print("\nTry to run the focus program again.") - exit(0) - -import fcntl -import errno - -# # Type -# v4l2.V4L2_CTRL_TYPE_INTEGER -# v4l2.V4L2_CTRL_TYPE_BOOLEAN -# v4l2.V4L2_CTRL_TYPE_MENU -# v4l2.V4L2_CTRL_TYPE_BUTTON -# v4l2.V4L2_CTRL_TYPE_INTEGER64 -# v4l2.V4L2_CTRL_TYPE_CTRL_CLASS -# # Flags -# v4l2.V4L2_CTRL_FLAG_DISABLED -# v4l2.V4L2_CTRL_FLAG_GRABBED -# v4l2.V4L2_CTRL_FLAG_READ_ONLY -# v4l2.V4L2_CTRL_FLAG_UPDATE -# v4l2.V4L2_CTRL_FLAG_INACTIVE -# v4l2.V4L2_CTRL_FLAG_SLIDER - -def assert_valid_queryctrl(queryctrl): - return queryctrl.type & ( - v4l2.V4L2_CTRL_TYPE_INTEGER - | v4l2.V4L2_CTRL_TYPE_BOOLEAN - | v4l2.V4L2_CTRL_TYPE_MENU - | v4l2.V4L2_CTRL_TYPE_BUTTON - | v4l2.V4L2_CTRL_TYPE_INTEGER64 - | v4l2.V4L2_CTRL_TYPE_CTRL_CLASS - | 7 - | 8 - | 9 - ) and queryctrl.flags & ( - v4l2.V4L2_CTRL_FLAG_DISABLED - | v4l2.V4L2_CTRL_FLAG_GRABBED - | v4l2.V4L2_CTRL_FLAG_READ_ONLY - | v4l2.V4L2_CTRL_FLAG_UPDATE - | v4l2.V4L2_CTRL_FLAG_INACTIVE - | v4l2.V4L2_CTRL_FLAG_SLIDER - ) - -def get_device_controls_menu(fd, queryctrl): - querymenu = v4l2.v4l2_querymenu(queryctrl.id, queryctrl.minimum) - while querymenu.index <= queryctrl.maximum: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYMENU, querymenu) - yield querymenu - querymenu.index += 1 - -def get_device_controls_by_class(fd, control_class): - # enumeration by control class - queryctrl = v4l2.v4l2_queryctrl(control_class | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) - while True: - try: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) - except IOError as e: - assert e.errno == errno.EINVAL - break - if v4l2.V4L2_CTRL_ID2CLASS(queryctrl.id) != control_class: - break - yield queryctrl - queryctrl = v4l2.v4l2_queryctrl(queryctrl.id | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) - -def getdict(struct): - val = dict((field, getattr(struct, field)) for field, _ in struct._fields_) - val.pop("reserved") - return val - -def get_device_controls(fd): - # original enumeration method - queryctrl = v4l2.v4l2_queryctrl(v4l2.V4L2_CID_BASE) - while queryctrl.id < v4l2.V4L2_CID_LASTP1: - try: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) - print(queryctrl.name) - except IOError as e: - # this predefined control is not supported by this device - assert e.errno == errno.EINVAL - queryctrl.id += 1 - continue - queryctrl = v4l2.v4l2_queryctrl(queryctrl.id + 1) - -def get_ctrls(vd): - ctrls = [] - # enumeration by control class - for class_ in (v4l2.V4L2_CTRL_CLASS_USER, v4l2.V4L2_CTRL_CLASS_MPEG, v4l2.V4L2_CTRL_CLASS_CAMERA): - for queryctrl in get_device_controls_by_class(vd, class_): - ctrl = getdict(queryctrl) - if queryctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: - ctrl["menu"] = [] - for querymenu in get_device_controls_menu(vd, queryctrl): - # print(querymenu.name) - ctrl["menu"].append(querymenu.name) - - if queryctrl.type == 9: - ctrl["menu"] = [] - for querymenu in get_device_controls_menu(vd, queryctrl): - ctrl["menu"].append(querymenu.index) - ctrls.append(ctrl) - return ctrls - -def set_ctrl(vd, id, value): - ctrl = v4l2.v4l2_control() - ctrl.id = id - ctrl.value = value - try: - fcntl.ioctl(vd, v4l2.VIDIOC_S_CTRL, ctrl) - except IOError as e: - print(e) - -def get_ctrl(vd, id): - ctrl = v4l2.v4l2_control() - ctrl.id = id - try: - fcntl.ioctl(vd, v4l2.VIDIOC_G_CTRL, ctrl) - except IOError as e: - print(e) - return None - return ctrl.value - - -class Focuser: - FOCUS_ID = 0x009a090a - dev = None - - def __init__(self, dev=0): - self.focus_value = 0 - self.dev = dev - - if type(dev) == int or (type(dev) == str and dev.isnumeric()): - self.dev = "/dev/video{}".format(dev) - - self.fd = open(self.dev, 'r') - self.ctrls = get_ctrls(self.fd) - self.hasFocus = False - for ctrl in self.ctrls: - if ctrl['id'] == Focuser.FOCUS_ID: - self.hasFocus = True - self.opts[Focuser.OPT_FOCUS]["MIN_VALUE"] = ctrl['minimum'] - self.opts[Focuser.OPT_FOCUS]["MAX_VALUE"] = ctrl['maximum'] - self.opts[Focuser.OPT_FOCUS]["DEF_VALUE"] = ctrl['default'] - self.focus_value = get_ctrl(self.fd, Focuser.FOCUS_ID) - - if not self.hasFocus: - raise RuntimeError("Device {} has no focus_absolute control.".format(self.dev)) - - def read(self): - return self.focus_value - - def write(self, value): - self.focus_value = value - # os.system("v4l2-ctl -d {} -c focus_absolute={}".format(self.dev, value)) - set_ctrl(self.fd, Focuser.FOCUS_ID, value) - - OPT_BASE = 0x1000 - OPT_FOCUS = OPT_BASE | 0x01 - OPT_ZOOM = OPT_BASE | 0x02 - OPT_MOTOR_X = OPT_BASE | 0x03 - OPT_MOTOR_Y = OPT_BASE | 0x04 - OPT_IRCUT = OPT_BASE | 0x05 - opts = { - OPT_FOCUS : { - "MIN_VALUE": 0, - "MAX_VALUE": 1000, - "DEF_VALUE": 0, - }, - } - def reset(self,opt,flag = 1): - info = self.opts[opt] - if info == None or info["DEF_VALUE"] == None: - return - self.set(opt,info["DEF_VALUE"]) - - def get(self,opt,flag = 0): - info = self.opts[opt] - return self.read() - - def set(self,opt,value,flag = 1): - info = self.opts[opt] - if value > info["MAX_VALUE"]: - value = info["MAX_VALUE"] - elif value < info["MIN_VALUE"]: - value = info["MIN_VALUE"] - self.write(value) - print("write: {}".format(value)) - - def __del__(self): - self.fd.close() - -pass diff --git a/update/main/Arducam.py b/update/main/Arducam.py deleted file mode 100644 index 941e07b..0000000 --- a/update/main/Arducam.py +++ /dev/null @@ -1,202 +0,0 @@ -import time -import os - -try: - import v4l2 -except Exception as e: - print(e) - print("Try to install v4l2-fix") - try: - from pip import main as pipmain - except ImportError: - from pip._internal import main as pipmain - pipmain(['install', 'v4l2-fix']) - print("\nTry to run the focus program again.") - exit(0) - -import fcntl -import errno - -# # Type -# v4l2.V4L2_CTRL_TYPE_INTEGER -# v4l2.V4L2_CTRL_TYPE_BOOLEAN -# v4l2.V4L2_CTRL_TYPE_MENU -# v4l2.V4L2_CTRL_TYPE_BUTTON -# v4l2.V4L2_CTRL_TYPE_INTEGER64 -# v4l2.V4L2_CTRL_TYPE_CTRL_CLASS -# # Flags -# v4l2.V4L2_CTRL_FLAG_DISABLED -# v4l2.V4L2_CTRL_FLAG_GRABBED -# v4l2.V4L2_CTRL_FLAG_READ_ONLY -# v4l2.V4L2_CTRL_FLAG_UPDATE -# v4l2.V4L2_CTRL_FLAG_INACTIVE -# v4l2.V4L2_CTRL_FLAG_SLIDER - -def assert_valid_queryctrl(queryctrl): - return queryctrl.type & ( - v4l2.V4L2_CTRL_TYPE_INTEGER - | v4l2.V4L2_CTRL_TYPE_BOOLEAN - | v4l2.V4L2_CTRL_TYPE_MENU - | v4l2.V4L2_CTRL_TYPE_BUTTON - | v4l2.V4L2_CTRL_TYPE_INTEGER64 - | v4l2.V4L2_CTRL_TYPE_CTRL_CLASS - | 7 - | 8 - | 9 - ) and queryctrl.flags & ( - v4l2.V4L2_CTRL_FLAG_DISABLED - | v4l2.V4L2_CTRL_FLAG_GRABBED - | v4l2.V4L2_CTRL_FLAG_READ_ONLY - | v4l2.V4L2_CTRL_FLAG_UPDATE - | v4l2.V4L2_CTRL_FLAG_INACTIVE - | v4l2.V4L2_CTRL_FLAG_SLIDER - ) - -def get_device_controls_menu(fd, queryctrl): - querymenu = v4l2.v4l2_querymenu(queryctrl.id, queryctrl.minimum) - while querymenu.index <= queryctrl.maximum: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYMENU, querymenu) - yield querymenu - querymenu.index += 1 - -def get_device_controls_by_class(fd, control_class): - # enumeration by control class - queryctrl = v4l2.v4l2_queryctrl(control_class | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) - while True: - try: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) - except IOError as e: - assert e.errno == errno.EINVAL - break - if v4l2.V4L2_CTRL_ID2CLASS(queryctrl.id) != control_class: - break - yield queryctrl - queryctrl = v4l2.v4l2_queryctrl(queryctrl.id | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) - -def getdict(struct): - val = dict((field, getattr(struct, field)) for field, _ in struct._fields_) - val.pop("reserved") - return val - -def get_device_controls(fd): - # original enumeration method - queryctrl = v4l2.v4l2_queryctrl(v4l2.V4L2_CID_BASE) - while queryctrl.id < v4l2.V4L2_CID_LASTP1: - try: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) - print(queryctrl.name) - except IOError as e: - # this predefined control is not supported by this device - assert e.errno == errno.EINVAL - queryctrl.id += 1 - continue - queryctrl = v4l2.v4l2_queryctrl(queryctrl.id + 1) - -def get_ctrls(vd): - ctrls = [] - # enumeration by control class - for class_ in (v4l2.V4L2_CTRL_CLASS_USER, v4l2.V4L2_CTRL_CLASS_MPEG, v4l2.V4L2_CTRL_CLASS_CAMERA): - for queryctrl in get_device_controls_by_class(vd, class_): - ctrl = getdict(queryctrl) - if queryctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: - ctrl["menu"] = [] - for querymenu in get_device_controls_menu(vd, queryctrl): - # print(querymenu.name) - ctrl["menu"].append(querymenu.name) - - if queryctrl.type == 9: - ctrl["menu"] = [] - for querymenu in get_device_controls_menu(vd, queryctrl): - ctrl["menu"].append(querymenu.index) - ctrls.append(ctrl) - return ctrls - -def set_ctrl(vd, id, value): - ctrl = v4l2.v4l2_control() - ctrl.id = id - ctrl.value = value - try: - fcntl.ioctl(vd, v4l2.VIDIOC_S_CTRL, ctrl) - except IOError as e: - print(e) - -def get_ctrl(vd, id): - ctrl = v4l2.v4l2_control() - ctrl.id = id - try: - fcntl.ioctl(vd, v4l2.VIDIOC_G_CTRL, ctrl) - except IOError as e: - print(e) - return None - return ctrl.value - - -class Focuser: - FOCUS_ID = 0x009a090a - dev = None - - def __init__(self, dev=0): - self.focus_value = 0 - self.dev = dev - - if type(dev) == int or (type(dev) == str and dev.isnumeric()): - self.dev = "/dev/video{}".format(dev) - - self.fd = open(self.dev, 'r') - self.ctrls = get_ctrls(self.fd) - self.hasFocus = False - for ctrl in self.ctrls: - if ctrl['id'] == Focuser.FOCUS_ID: - self.hasFocus = True - self.opts[Focuser.OPT_FOCUS]["MIN_VALUE"] = ctrl['minimum'] - self.opts[Focuser.OPT_FOCUS]["MAX_VALUE"] = ctrl['maximum'] - self.opts[Focuser.OPT_FOCUS]["DEF_VALUE"] = ctrl['default'] - self.focus_value = get_ctrl(self.fd, Focuser.FOCUS_ID) - - if not self.hasFocus: - raise RuntimeError("Device {} has no focus_absolute control.".format(self.dev)) - - def read(self): - return self.focus_value - - def write(self, value): - self.focus_value = value - # os.system("v4l2-ctl -d {} -c focus_absolute={}".format(self.dev, value)) - set_ctrl(self.fd, Focuser.FOCUS_ID, value) - - OPT_BASE = 0x1000 - OPT_FOCUS = OPT_BASE | 0x01 - OPT_ZOOM = OPT_BASE | 0x02 - OPT_MOTOR_X = OPT_BASE | 0x03 - OPT_MOTOR_Y = OPT_BASE | 0x04 - OPT_IRCUT = OPT_BASE | 0x05 - opts = { - OPT_FOCUS : { - "MIN_VALUE": 0, - "MAX_VALUE": 1000, - "DEF_VALUE": 0, - }, - } - def reset(self,opt,flag = 1): - info = self.opts[opt] - if info == None or info["DEF_VALUE"] == None: - return - self.set(opt,info["DEF_VALUE"]) - - def get(self,opt,flag = 0): - info = self.opts[opt] - return self.read() - - def set(self,opt,value,flag = 1): - info = self.opts[opt] - if value > info["MAX_VALUE"]: - value = info["MAX_VALUE"] - elif value < info["MIN_VALUE"]: - value = info["MIN_VALUE"] - self.write(value) - print("write: {}".format(value)) - - def __del__(self): - self.fd.close() - -pass diff --git a/update/main/config.txt b/update/main/config.txt deleted file mode 100644 index ce06bd8..0000000 --- a/update/main/config.txt +++ /dev/null @@ -1,85 +0,0 @@ -# For more options and information see -# http://rpf.io/configtxt -# Some settings may impact device functionality. See link above for details - - -# uncomment if you get no picture on HDMI for a default "safe" mode -#hdmi_safe=1 -hdmi_blanking=2 - -# uncomment the following to adjust overscan. Use positive numbers if console -# goes off screen, and negative if there is too much border -#overscan_left=16 -#overscan_right=16 -#overscan_top=16 -#overscan_bottom=16 - -# uncomment to force a console size. By default it will be display's size minus -# overscan. -#framebuffer_width=1280 -#framebuffer_height=720 - -# uncomment if hdmi display is not detected and composite is being output -#hdmi_force_hotplug=1 - -# uncomment to force a specific HDMI mode (this will force VGA) -#hdmi_group=1 -#hdmi_mode=1 - -# uncomment to force a HDMI mode rather than DVI. This can make audio work in -# DMT (computer monitor) modes -#hdmi_drive=2 - -# uncomment to increase signal to HDMI, if you have interference, blanking, or -# no display -#config_hdmi_boost=4 - -# uncomment for composite PAL -#sdtv_mode=2 - -#uncomment to overclock the arm. 700 MHz is the default. -#arm_freq=800 - -# Uncomment some or all of these to enable the optional hardware interfaces -#dtparam=i2c_arm=on -#dtparam=i2s=on -#dtparam=spi=on - -# Uncomment this to enable infrared communication. -#dtoverlay=gpio-ir,gpio_pin=17 -#dtoverlay=gpio-ir-tx,gpio_pin=18 - -# Additional overlays and parameters are documented /boot/overlays/README - -# Enable audio (loads snd_bcm2835) -dtparam=audio=on - -# Automatically load overlays for detected cameras -camera_auto_detect=0 - -# Automatically load overlays for detected DSI displays -display_auto_detect=1 - -# Enable DRM VC4 V3D driver -#dtoverlay=vc4-kms-v3d -max_framebuffers=2 - -# Disable compensation for displays with overscan -disable_overscan=1 - -[cm4] -# Enable host mode on the 2711 built-in XHCI USB controller. -# This line should be removed if the legacy DWC2 controller is required -# (e.g. for USB device mode) or if USB support is not required. -otg_mode=1 - -[pi4] -# Run as fast as firmware / board allows -arm_boost=1 - -[all] - -camera_auto_detect=0 -gpu_mem=256 -dtoverlay=vc4-fkms-v3d -dtoverlay=imx519,media-controller=1 diff --git a/update/main/fla.py b/update/main/fla.py deleted file mode 100644 index 5fe4649..0000000 --- a/update/main/fla.py +++ /dev/null @@ -1,149 +0,0 @@ -from flask import Flask, make_response, jsonify, request, abort -from PIL import Image -import gphoto2 as gp -from time import sleep, time -import shutil -from OpenScan import load_int, load_float, load_bool, ringlight -import RPi.GPIO as GPIO -from math import sqrt -import os - -GPIO.setwarnings(False) -GPIO.setmode(GPIO.BCM) - -app = Flask(__name__) - -basedir = '/home/pi/OpenScan/' -timer = time() - -################################################################################################################### -@app.route('/shutdown', methods=['get']) -def shutdown(): - delay = 0.1 - ringlight(2,False) - - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) - os.system('shutdown -h now') -################################################################################################################### -@app.route('/reboot', methods=['get']) -def reboot(): - delay = 0.1 - ringlight(2,False) - - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) - - os.system('reboot -h') -################################################################################################################### -@app.route('/ping', methods=['get']) -def ping(): - global timer - cmd = str(request.args.get('cmd')) - if cmd == 'set': - timer = time() - inactive = time() - timer - return ({'inactive':inactive}, 200) -################################################################################################################### -@app.route('/gphoto_init', methods=['get']) -def gphoto_init(): - global camera - camera = gp.Camera() - camera.init() - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_preview', methods=['get']) -def gphoto_preview(): - filepath = str(request.args.get('filepath')) - camera_file = gp.gp_camera_capture_preview(camera)[1] - target = basedir + filepath - camera_file.save(target) - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_capture', methods=['get']) -def gphoto_capture(): - filepath = str(request.args.get('filepath')) - file_path = camera.capture(gp.GP_CAPTURE_IMAGE) - camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) - camera_file.save(basedir + filepath) - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_test', methods=['get']) -def gphoto_test(): - text = camera.get_summary() - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_exit', methods=['get']) -def gphoto_exit(): - global camera - camera.exit() - return ({}, 200) -################################################################################################################### -@app.route('/crop', methods=['get']) -def crop(): - output_downscale = load_bool('cam_output_downscale') - output_resolution = load_int('cam_output_resolution') - preview_resolution = load_int('cam_preview_resolution') - filepath_in = basedir + str(request.args.get('filepath_in')) - filepath_out = basedir + str(request.args.get('filepath_out')) - cropx = int(request.args.get('cropx'))/200 - cropy = int(request.args.get('cropy'))/200 - rotation = int(request.args.get('rotation')) - preview = str(request.args.get('preview')) - downscale = 1 - - with Image.open(filepath_in) as img: - w,h = img.size - if cropx != 0 or cropy != 0: - img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) - if rotation == 90: - img = img.transpose(Image.ROTATE_90) - elif rotation == 180: - img= img.transpose(Image.ROTATE_180) - elif rotation == 270: - img= img.transpose(Image.ROTATE_270) - - if preview == "True": - w,h = img.size - factor = (w*h)/preview_resolution - if factor > 1: - img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) - - elif output_downscale == True: - w,h = img.size - factor = (w*h)/output_resolution - if factor > 1: - img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) - - img.save(filepath_out, quality=95, subsampling=0) - - return ({}, 200) - -################################################################################################################### -@app.route('/external_capture', methods=['get']) -def external_capture(): - pin = load_int('pin_external') - delay_before = load_float('cam_delay_before') - timeout = load_float('cam_timeout')/1000 - delay_after = load_float('cam_delay_after') - GPIO.setup(pin, GPIO.OUT) - GPIO.output(pin, GPIO.LOW) - sleep(delay_before) - GPIO.output(pin, GPIO.HIGH) - sleep(timeout) - GPIO.output(pin, GPIO.LOW) - sleep(delay_after) - return ({}, 200) - - - - -if __name__ == '__main__': -# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) - app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/betaArdu/OpenScan.py b/update/meanwhile/OpenScan.py similarity index 97% rename from update/betaArdu/OpenScan.py rename to update/meanwhile/OpenScan.py index e0edd35..681c78d 100644 --- a/update/betaArdu/OpenScan.py +++ b/update/meanwhile/OpenScan.py @@ -165,7 +165,14 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): step_count=-step_count for x in range(step_count): if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: - break + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + GPIO.output(steppin, GPIO.HIGH) if x<=ramp and x<=step_count/2: delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) diff --git a/update/betaArdu/config.txt b/update/meanwhile/config.txt old mode 100644 new mode 100755 similarity index 100% rename from update/betaArdu/config.txt rename to update/meanwhile/config.txt diff --git a/update/betaArdu/fla.py b/update/meanwhile/fla.py similarity index 89% rename from update/betaArdu/fla.py rename to update/meanwhile/fla.py index 7fd27dd..57f4660 100644 --- a/update/betaArdu/fla.py +++ b/update/meanwhile/fla.py @@ -1,4 +1,4 @@ -from flask import Flask, make_response, jsonify, request, abort +from flask import Flask, make_response, jsonify, request, abort, redirect from picamera2 import Picamera2 from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time @@ -11,6 +11,7 @@ from skimage import io, feature, color, transform import numpy as np from scipy import ndimage +import socket GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) @@ -20,6 +21,7 @@ basedir = '/home/pi/OpenScan/' timer = time() cam_mode = 0 +hostname = socket.gethostname().split(":") def overlay_mask(image, mask_image): # Ensure image is in RGB mode @@ -69,28 +71,44 @@ def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilatio ################################################################################################################### @app.route('/shutdown', methods=['get']) def shutdown(): - delay = 0.1 - ringlight(2,False) - - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) - os.system('shutdown -h now') + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + os.system('shutdown -h now') + + else: + return redirect("http://" + hostname, code=302) ################################################################################################################### @app.route('/reboot', methods=['get']) def reboot(): - delay = 0.1 - ringlight(2,False) - - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) - - os.system('reboot -h') + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + + os.system('reboot -h') + else: + return redirect("http://" + hostname, code=302) ################################################################################################################### def plot_orb_keypoints(pil_image): @@ -323,6 +341,11 @@ def picam2_af(): picam2.set_controls({"AfMode": 1 ,"AfTrigger": 0}) # --> wait 3-5s return ({}, 200) +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + if __name__ == '__main__': # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/main/flows.json b/update/meanwhile/flows.json.tmpl similarity index 58% rename from update/main/flows.json rename to update/meanwhile/flows.json.tmpl index b79939e..20fb35f 100644 --- a/update/main/flows.json +++ b/update/meanwhile/flows.json.tmpl @@ -1,55 +1,66 @@ [ { - "id": "829d803b6033a693", + "id": "e6f4d02efb300ea9", "type": "tab", - "label": "HOME", + "label": "Init", "disabled": false, "info": "", "env": [] }, { - "id": "1613373abaf77a2c", + "id": "481edaf6db5a7a54", "type": "tab", - "label": "SCAN", + "label": "Scan", "disabled": false, "info": "", "env": [] }, { - "id": "4981d84ef1a366d1", + "id": "80a3942785a26c29", "type": "tab", - "label": "Files&Cloud", + "label": "Files", "disabled": false, "info": "", "env": [] }, { - "id": "017bd4e4a428bee5", + "id": "e43a27722b508115", "type": "tab", - "label": "SETTINGS", + "label": "Settings", "disabled": false, "info": "", "env": [] }, { - "id": "c8e7ecb5849edb9a", + "id": "a5557543ccff5889", "type": "tab", - "label": "UPDATE", + "label": "Update", "disabled": false, "info": "", "env": [] }, { - "id": "b3150b13e34b1fe8", + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", "type": "ui_tab", - "name": "OpenScan", + "name": "Scan", "icon": "dashboard", - "order": 1, + "order": 2, "disabled": false, "hidden": false }, { - "id": "b6e9c2df6b28ff66", + "id": "5c06cb6bcc371ee6", "type": "ui_base", "theme": { "name": "theme-dark", @@ -61,8 +72,8 @@ "reset": false }, "darkTheme": { - "default": "#097479", - "baseColor": "#097479", + "default": "{{ darktheme-default }}", + "baseColor": "{{ darktheme-basecolor }}", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", "edited": true, "reset": false @@ -71,16 +82,17 @@ "name": "Untitled Theme 1", "default": "#4B7930", "baseColor": "#4B7930", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false }, "themeState": { "base-color": { - "default": "#097479", - "value": "#097479", + "default": "{{ base-color-default }}", + "value": "{{ base-color-value }}", "edited": false }, "page-titlebar-backgroundColor": { - "value": "#097479", + "value": "{{ page-titlebar-bgcolor }}", "edited": false }, "page-backgroundColor": { @@ -108,7 +120,7 @@ "edited": false }, "widget-backgroundColor": { - "value": "#097479", + "value": "{{ widget-bgcolor }}", "edited": false }, "widget-borderColor": { @@ -128,190 +140,123 @@ } }, "site": { - "name": "OpenScan 3D Scanner", + "name": "OpenScan", "hideToolbar": "false", "allowSwipe": "false", "lockMenu": "false", "allowTempTheme": "true", "dateFormat": "DD/MM/YYYY", "sizes": { - "sx": 46, - "sy": 46, - "gx": 10, - "gy": 10, + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, "cx": 6, "cy": 6, - "px": 6, - "py": 6 + "px": 0, + "py": 0 } } }, { - "id": "729f9ea6e3513c9b", - "type": "ui_group", - "name": "Home", - "tab": "b3150b13e34b1fe8", - "order": 2, - "disp": false, - "width": "6", - "collapse": false, - "className": "" + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 }, { - "id": "65ae49b64fa0d83e", - "type": "ui_tab", - "name": "Settings", - "icon": "dashboard", - "order": 4, - "disabled": false, - "hidden": false + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 }, { - "id": "4fe6b4c0ade0938a", + "id": "b5fdd57b.15eda8", "type": "ui_group", - "name": "General", - "tab": "65ae49b64fa0d83e", + "name": "Main", + "tab": "15a222ed.d70a7d", "order": 1, - "disp": true, - "width": "6", - "collapse": true, - "className": "" + "disp": false, + "width": 13, + "collapse": false }, { - "id": "0fe66c9190b8a87c", + "id": "db43d646.2074c8", "type": "ui_group", - "name": "Network", - "tab": "65ae49b64fa0d83e", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", "order": 2, "disp": true, "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "93aadb71dee6d977", - "type": "ui_group", - "name": "Camera", - "tab": "65ae49b64fa0d83e", - "order": 4, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "d49a6dfd7fb17096", - "type": "ui_group", - "name": "Motor", - "tab": "65ae49b64fa0d83e", - "order": 5, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "644b3bcc903d46ca", - "type": "ui_group", - "name": "Pinout", - "tab": "65ae49b64fa0d83e", - "order": 6, - "disp": true, - "width": "6", - "collapse": true, - "className": "" + "collapse": false }, { - "id": "e23b837a9f040895", + "id": "15a222ed.d70a7d", "type": "ui_tab", - "name": "Scan", + "name": "Files&Cloud", "icon": "dashboard", - "order": 2, + "order": 3, "disabled": false, "hidden": false }, { - "id": "7aaf184330605300", + "id": "365a30d0dfa83e95", "type": "ui_group", - "name": "Settings", + "name": "settings", "tab": "e23b837a9f040895", "order": 1, "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "ce9cc9d915dc6eb6", - "type": "ui_group", - "name": "Picamera", - "tab": "e23b837a9f040895", - "order": 2, - "disp": false, - "width": "12", + "width": 7, "collapse": false, "className": "" }, { - "id": "90223f7ddc082321", + "id": "ac7409105cfecac6", "type": "ui_group", - "name": "Arducam", + "name": "advanced", "tab": "e23b837a9f040895", "order": 3, "disp": false, - "width": 12, + "width": 7, "collapse": false, "className": "" }, { - "id": "7625f9c9e8dbc5c6", - "type": "ui_spacer", - "z": "017bd4e4a428bee5", - "name": "spacer", - "group": "", - "order": 4, - "width": 1, - "height": 1 - }, - { - "id": "3b4bd36726be16d5", + "id": "729f9ea6e3513c9b", "type": "ui_group", - "name": "OpenScanCloud", - "tab": "65ae49b64fa0d83e", - "order": 3, - "disp": true, + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, "width": "6", "collapse": false, "className": "" }, { - "id": "b5fdd57b.15eda8", + "id": "5b3e5aca21140e9a", "type": "ui_group", - "name": "Main", - "tab": "15a222ed.d70a7d", + "name": "Update", + "tab": "b3150b13e34b1fe8", "order": 1, "disp": false, - "width": 13, - "collapse": false - }, - { - "id": "db43d646.2074c8", - "type": "ui_group", - "name": "OpenScanCloud", - "tab": "15a222ed.d70a7d", - "order": 2, - "disp": true, "width": "6", - "collapse": false + "collapse": false, + "className": "" }, { - "id": "15a222ed.d70a7d", + "id": "b3150b13e34b1fe8", "type": "ui_tab", - "name": "Files&Cloud", + "name": "OpenScan", "icon": "dashboard", - "order": 3, + "order": 1, "disabled": false, - "hidden": false + "hidden": true }, { "id": "ddbd496e.93a288", @@ -339,285 +284,284 @@ "type": "ui_tab", "name": "Update & Info", "icon": "dashboard", - "order": 5, + "order": 4, "disabled": false, "hidden": false }, { - "id": "1f7f7e1e24f5ad9b", + "id": "4390b2ebcbbe104c", "type": "ui_group", - "name": "Initialize", - "tab": "b3150b13e34b1fe8", - "order": 3, - "disp": false, + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, "width": "6", - "collapse": false, + "collapse": true, "className": "" }, { - "id": "5b3e5aca21140e9a", + "id": "8ab79a98e536e0d6", "type": "ui_group", - "name": "Update", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, "width": "6", "collapse": false, "className": "" }, { - "id": "700f47327133ab68", + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", "type": "ui_spacer", - "z": "829d803b6033a693", + "z": "e43a27722b508115", "name": "spacer", - "group": "729f9ea6e3513c9b", - "order": 6, + "group": "db43d646.2074c8", + "order": 1, "width": 1, "height": 1 }, { - "id": "ebf828f29201a53b", + "id": "33b6d7317d1524b8", "type": "ui_spacer", - "z": "829d803b6033a693", + "z": "e43a27722b508115", "name": "spacer", - "group": "729f9ea6e3513c9b", - "order": 8, + "group": "db43d646.2074c8", + "order": 3, "width": 1, "height": 1 }, { - "id": "3b4961c4e72ff58a", + "id": "aaf5b874c52a58aa", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "4fe6b4c0ade0938a", - "order": 6, - "width": 6, + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, "height": 1 }, { - "id": "5ef40dca2c6c6aab", + "id": "f8d8740dcbf499fb", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "4fe6b4c0ade0938a", + "group": "365a30d0dfa83e95", "order": 11, - "width": 6, + "width": 1, "height": 1 }, { - "id": "bdd26746cc1e1ba0", + "id": "7ac0cb556740d159", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "3b4bd36726be16d5", - "order": 6, - "width": 2, + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, "height": 1 }, { - "id": "3584b5ef2b7acb72", + "id": "4de2414e29020c74", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "3b4bd36726be16d5", - "order": 8, - "width": 2, + "group": "90223f7ddc082321", + "order": 2, + "width": 7, "height": 1 }, { - "id": "cac67f0e.f01fa", - "type": "ui_group", - "name": "Button Top", - "tab": "", - "order": 1, - "disp": true, - "width": "6", - "collapse": false + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 }, { - "id": "b73c392ffd8ca3f2", + "id": "ce21673092264c38", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "90223f7ddc082321", - "order": 14, - "width": 2, + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, "height": 1 }, { - "id": "89fe04171cd2f35b", + "id": "3f7b77f8a1675d27", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "90223f7ddc082321", - "order": 15, - "width": 2, + "group": "12b719cba49817c9", + "order": 7, + "width": 4, "height": 1 }, { - "id": "80c9c0059de08f02", + "id": "0799b02d12fc3a14", "type": "ui_spacer", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "spacer", - "group": "90223f7ddc082321", - "order": 16, - "width": 2, + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, "height": 1 }, { - "id": "3fe52603e2ac73b6", - "type": "ui_template", - "z": "829d803b6033a693", - "group": "729f9ea6e3513c9b", - "name": "Background", - "order": 1, - "width": 0, - "height": 0, - "format": "", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": false, - "templateScope": "global", - "className": "", - "x": 110, - "y": 40, - "wires": [ - [] - ] + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" }, - { - "id": "4468f691.103eb8", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 2, - "width": 3, - "height": 2, - "passthru": false, - "label": "SCAN", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "1", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 100, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] +{ + "id": "15edc2ce885dddb3", + "type": "ui_group", + "name": "Colorines", + "tab": "457102eadc9ddb6c", + "order": 8, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, +{ + "id": "33aff36289823faa", + "type": "ui_group", + "name": "Monitoring", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, + "width": "6", + "collapse": false, + "className": "" }, { - "id": "6560dd25.9e76c4", - "type": "ui_button", - "z": "829d803b6033a693", + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", "name": "", - "group": "729f9ea6e3513c9b", - "order": 4, - "width": 3, - "height": 2, - "passthru": false, - "label": "Settings", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "3", - "payloadType": "num", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, "topic": "", - "topicType": "str", + "payload": "", + "payloadType": "date", "x": 100, - "y": 180, + "y": 460, "wires": [ [ - "62cd5288.2805fc" + "949bafced17d66d6" ] ] }, { - "id": "62cd5288.2805fc", - "type": "ui_ui_control", - "z": "829d803b6033a693", - "name": "", - "events": "all", - "x": 280, - "y": 100, + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, "wires": [ [] ] }, { - "id": "71e72293.91c6fc", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 3, - "width": 3, - "height": 2, - "passthru": false, - "label": "Files", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "2", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 140, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "e7306ef2.3b4df", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 5, - "width": 3, - "height": 2, - "passthru": false, - "label": "Update&Info", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "4", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 110, - "y": 220, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "88edad7ca53698fd", + "id": "a1f0ed7d5a9d670e", "type": "inject", - "z": "829d803b6033a693", - "name": "1s", + "z": "e6f4d02efb300ea9", + "name": "", "props": [ { - "p": "payload" + "p": "overwrite", + "v": "false", + "vt": "bool" }, { "p": "topic", @@ -627,112 +571,60 @@ "repeat": "", "crontab": "", "once": true, - "onceDelay": "1", + "onceDelay": "0.1", "topic": "", - "payload": "true", - "payloadType": "bool", - "x": 90, - "y": 400, + "x": 110, + "y": 60, "wires": [ [ - "000a811a215e08d4", - "83c2b5ea51f0fec3", - "88fde4ab78c965d7", - "bee62d2a99cbc63b", - "8e39e4a037487ecd", - "bb84b9e5c7d8e21f", - "7113d7b25a851151", - "c4c1580c289fc7bd" + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" ] ] }, { - "id": "bd75f33b8a57c522", - "type": "link out", - "z": "829d803b6033a693", - "name": "enable", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "92c98e6ce7cd25f9", - "b33d604c.5f1a6" - ], - "x": 335, - "y": 440, - "wires": [] - }, - { - "id": "000a811a215e08d4", + "id": "544d20f02215011a", "type": "function", - "z": "829d803b6033a693", - "name": "enable", - "func": "msg.enabled = true\nmsg.payload = 1\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 230, - "y": 440, + "x": 330, + "y": 60, "wires": [ [ - "bd75f33b8a57c522" + "c77552216a8bb781" ] ] }, { - "id": "83c2b5ea51f0fec3", - "type": "function", - "z": "829d803b6033a693", - "name": "disable", - "func": "msg.enabled = false\nreturn msg", + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 240, - "y": 480, + "x": 540, + "y": 60, "wires": [ [ - "6b94bf2295b1b31d" + "960912e90ba5b5bc" ] ] }, - { - "id": "6b94bf2295b1b31d", - "type": "link out", - "z": "829d803b6033a693", - "name": "disable", - "mode": "link", - "links": [ - "a1d29e56599da0bd" - ], - "x": 335, - "y": 480, - "wires": [] - }, - { - "id": "88fde4ab78c965d7", - "type": "function", - "z": "829d803b6033a693", - "name": "write", - "func": "var file = 'status_cloud'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\ncontent = 'ready'\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 520, - "wires": [ - [] - ] - }, { "id": "960912e90ba5b5bc", "type": "link out", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "started1s", "mode": "link", "links": [ @@ -752,16 +644,43 @@ "e5f38b4a07a5e278", "f0b355967b33dfee", "d0104e0163745993", - "5e7d5e4335d37794" + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" ], - "x": 615, - "y": 800, + "x": 645, + "y": 60, "wires": [] }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, { "id": "168d72a54504b327", "type": "inject", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "5/0.1s", "props": [ { @@ -780,7 +699,7 @@ "payload": "", "payloadType": "str", "x": 100, - "y": 720, + "y": 380, "wires": [ [ "6c6ef2255a7d39e5" @@ -790,64 +709,106 @@ { "id": "6c6ef2255a7d39e5", "type": "link out", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "repeat 5s/0.1s", "mode": "link", "links": [ "61990987acd0f263", - "2415272f42ce468c" + "2415272f42ce468c", + "6bf8344af427a6ba" ], - "x": 195, - "y": 720, + "x": 205, + "y": 380, "wires": [] }, { - "id": "bee62d2a99cbc63b", - "type": "function", - "z": "829d803b6033a693", - "name": "global", - "func": "global.set('flag_pw', true)\nglobal.set('flag', true)\nglobal.set('combine', false)\nglobal.set('focus', 2838)\nglobal.set('focus1', 0)\nglobal.set('focus2', 0)\n\nglobal.set('focuser', true)\n", + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 400, + "x": 270, + "y": 140, "wires": [ [ - "f20da2fc4978b7bf" + "eb1a2387a1eeea76" ] ] }, { - "id": "544d20f02215011a", + "id": "b1e2491c952f84c9", "type": "function", - "z": "829d803b6033a693", - "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cameras':{\n 'imx519':[4656,3496],\n 'imx219':[3280,2464],\n 'imx477':[4056,3040],\n 'ov5647':[2592,1944],\n 'imx378':[3840,2880],\n 'ov9271':[1280,800],\n 'imx290a':[1920,1080],\n 'imx290b':[1920,1080],\n },\n 'cam_AFmode':true,\n 'cam_STmode':true,\n 'cam_stacksize':2,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':0,\n 'cam_saturation':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'hostname':'',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n 'pin_endstop1':24,\n 'pin_endstop2':25,\n 'pin_external':10,\n 'pin_ringlight1':17,\n 'pin_ringlight2':27,\n 'pin_rotor_dir':5,\n 'pin_rotor_enable':23,\n 'pin_rotor_step':6,\n 'pin_tt_dir':9,\n 'pin_tt_enable':22,\n 'pin_tt_step':11,\n 'rotor_acc':1,\n 'rotor_accramp':2000,\n 'rotor_angle':10,\n 'rotor_anglemax':60,\n 'rotor_anglemin':-20,\n 'rotor_anglestart':0,\n 'rotor_delay':0.0001,\n 'rotor_dir':1,\n 'rotor_stepsperrotation':48000,\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n 'tt_acc':1,\n 'tt_accramp':200,\n 'tt_angle':10,\n 'tt_delay':0.0001,\n 'tt_dir':1,\n 'tt_stepsperrotation':3200,\n 'cam_focus':2838,\n 'cam_focus1':0,\n 'cam_focus2':0,\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'downscale_threshold':1000,\n 'turntable_mode':false,\n 'timeout_ringlight':300,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n}}\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 310, - "y": 800, + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, "wires": [ [ - "c77552216a8bb781" + "200d4b9951b6e066" ] ] }, { - "id": "a1f0ed7d5a9d670e", + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", "type": "inject", - "z": "829d803b6033a693", - "name": "", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", "props": [ { "p": "overwrite", - "v": "false", + "v": "true", "vt": "bool" }, { @@ -857,11 +818,11 @@ ], "repeat": "", "crontab": "", - "once": true, + "once": false, "onceDelay": "0.1", "topic": "", - "x": 90, - "y": 800, + "x": 800, + "y": 40, "wires": [ [ "544d20f02215011a" @@ -869,88 +830,181 @@ ] }, { - "id": "c77552216a8bb781", + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", "type": "python3-function", - "z": "829d803b6033a693", - "name": "chk files", - "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", "outputs": 1, - "x": 520, - "y": 800, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, "wires": [ [ - "960912e90ba5b5bc" + "62cd5288.2805fc" ] ] }, { - "id": "38783aea9cc317a6", - "type": "link in", - "z": "829d803b6033a693", - "name": "factory reset", - "links": [ - "80bccc884b0be297", - "beacc3dc5398fa79" - ], - "x": 135, - "y": 840, + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, "wires": [ [ - "544d20f02215011a" + "62cd5288.2805fc" ] ] }, { - "id": "f20da2fc4978b7bf", - "type": "link out", - "z": "829d803b6033a693", - "name": "global", - "mode": "link", - "links": [ - "d14bbbb446d45e39" - ], - "x": 345, - "y": 400, - "wires": [] + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] }, { - "id": "8e39e4a037487ecd", - "type": "python3-function", - "z": "829d803b6033a693", - "name": "create log", - "func": "import subprocess\nfrom time import sleep\nsleep(20)\n\n\nlog = '############################################DMESG############################################\\n'\nlog += subprocess.getoutput(\"dmesg\")\nlog += '\\n############################################SYSLOG############################################\\n'\nlog += subprocess.getoutput(\"tail -10000 /var/log/syslog\")\n\nwith open('/home/pi/OpenScan/tmp/log.txt', 'w+') as file:\n file.write(log)\n\nreturn msg", - "outputs": 1, - "x": 240, - "y": 560, + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, "wires": [ - [] + [ + "62cd5288.2805fc" + ] ] }, { - "id": "be8cae9cf6f3585f", - "type": "ui_template", - "z": "829d803b6033a693", - "group": "1f7f7e1e24f5ad9b", - "name": "first start", - "order": 1, - "width": 6, - "height": 3, - "format": "

Initial Setup

\n

Note, that you can always adjust these and other settings in the settings menu, which will appear after this setup stage. 

\n

Model

\n

Please select the OpenScan Version - this will only affect the motor settings (acceleration, gear ratio, speed).

\n

Camera

\n

- Pi Camera v1, v2, HQ, Arducam IMX519, IMX290, IMX378, OV9281 are connected through the ribbon cable. If you encounter any issues, please check the cable's orientation

\n

- DSLR (gphoto) - can be used with a wide range of cameras, which can be connected and controlled via USB. Check GPhoto if your camera is supported

\n

- External Camera - Can be used to connect your camera trigger to the GPIO pins on the front of the pi shield. This can be used with any (modified) remote shutter release, and thus it is possible to use Smartphones, DSLR and compact cameras

", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", "className": "", - "x": 280, - "y": 40, + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, "wires": [ - [] + [ + "62cd5288.2805fc" + ] ] }, { "id": "8955d11554f55e63", "type": "ui_button", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "", "group": "5b3e5aca21140e9a", "order": 1, @@ -968,7 +1022,7 @@ "topic": "", "topicType": "str", "x": 120, - "y": 280, + "y": 720, "wires": [ [ "1e7457ea9c2c5e09" @@ -978,961 +1032,410 @@ { "id": "1e7457ea9c2c5e09", "type": "link out", - "z": "829d803b6033a693", + "z": "e6f4d02efb300ea9", "name": "update", "mode": "link", "links": [ "39a502b38837273d" ], "x": 245, - "y": 280, - "wires": [] - }, - { - "id": "bb84b9e5c7d8e21f", - "type": "python3-function", - "z": "829d803b6033a693", - "name": "rescue incomplete project", - "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", - "outputs": 1, - "x": 290, - "y": 600, - "wires": [ - [] - ] - }, - { - "id": "a291fc98e4269c1b", - "type": "ui_text", - "z": "829d803b6033a693", - "group": "729f9ea6e3513c9b", - "order": 7, - "width": 4, - "height": 1, - "name": "version", - "label": "Version:", - "format": "{{msg.firmware}}", - "layout": "row-center", - "className": "", - "x": 460, - "y": 360, + "y": 720, "wires": [] }, { - "id": "7113d7b25a851151", + "id": "245e4341d4fb611c", "type": "function", - "z": "829d803b6033a693", - "name": "FIRMWARE VERSION", - "func": "msg.firmware = '2022-08-02'\nreturn msg", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 280, - "y": 360, + "x": 790, + "y": 540, "wires": [ [ - "a291fc98e4269c1b", - "ec5cefa70ff535f7" + "627406f3611511dc" ] ] }, { - "id": "ec5cefa70ff535f7", - "type": "ui_text", - "z": "829d803b6033a693", - "group": "ddbd496e.93a288", - "order": 2, - "width": 6, - "height": 1, - "name": "current version", - "label": "Current version:", - "format": "{{msg.firmware}}", - "layout": "row-spread", - "className": "", - "x": 480, - "y": 320, - "wires": [] - }, - { - "id": "c4c1580c289fc7bd", + "id": "627406f3611511dc", "type": "python3-function", - "z": "829d803b6033a693", - "name": "create path", - "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", "outputs": 1, - "x": 250, - "y": 640, - "wires": [ - [] - ] - }, - { - "id": "06d33bb8951ce668", - "type": "ui_template", - "z": "829d803b6033a693", - "group": "", - "name": "donate", - "order": 2, - "width": "0", - "height": "0", - "format": "\n\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "global", - "className": "", - "x": 450, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "828e5298.d2192", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 9, - "width": 2, - "height": 1, - "passthru": false, - "label": "⇐", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 400, - "wires": [ - [ - "b12e54fb.3141b8" - ] - ] - }, - { - "id": "96c7e241.458e6", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 10, - "width": 2, - "height": 1, - "passthru": false, - "label": "⇒", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 440, - "wires": [ - [ - "37f52dd4.bd7572" - ] - ] - }, - { - "id": "2e854876.6b6008", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 6, - "width": 2, - "height": 1, - "passthru": true, - "label": "⇑", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 280, - "wires": [ - [ - "555aea34.b3b5e4" - ] - ] - }, - { - "id": "753817f.1b9b3e8", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 7, - "width": 2, - "height": 1, - "passthru": true, - "label": "⇓", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 320, + "x": 930, + "y": 540, "wires": [ [ - "9905e0c9.dddcd" + "50eeb3e362f9027f" ] ] }, { - "id": "8775044.3aa3ef8", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 8, - "width": 2, - "height": 1, - "name": "", - "label": "Turntable", - "format": "", - "layout": "row-left", - "className": "", - "x": 100, - "y": 360, - "wires": [] - }, - { - "id": "9e8a2d23.bf6ce", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 5, - "width": 2, - "height": 1, + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", "name": "", - "label": "Rotor", - "format": "", - "layout": "row-left", - "className": "", - "x": 90, - "y": 240, - "wires": [] - }, - { - "id": "555aea34.b3b5e4", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 280, - "wires": [ - [ - "46e00b45.c24ca4" - ] - ] - }, - { - "id": "9905e0c9.dddcd", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 320, - "wires": [ - [ - "6ee089cb343a35ef" - ] - ] - }, - { - "id": "b12e54fb.3141b8", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 400, - "wires": [ - [ - "c1871a2b9af5419a" - ] - ] - }, - { - "id": "37f52dd4.bd7572", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 440, - "wires": [ - [ - "42b9f1fc49e69f54" - ] - ] - }, - { - "id": "46e00b45.c24ca4", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Rotor left", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('rotor',load_int('rotor_angle'))", - "outputs": 1, - "x": 360, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "6ee089cb343a35ef", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Rotor right", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('rotor',-load_int('rotor_angle'))", - "outputs": 1, - "x": 370, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "42b9f1fc49e69f54", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "TT right", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('tt',-load_int('tt_angle'))", - "outputs": 1, - "x": 360, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "c1871a2b9af5419a", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "TT left", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('tt',load_int('tt_angle'))", - "outputs": 1, - "x": 350, - "y": 400, - "wires": [ - [] - ] - }, - { - "id": "aebad788761dce4a", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "routine_photocount", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 14, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", "topic": "", - "topicType": "str", - "min": "10", - "max": "300", - "step": "10", - "className": "", - "x": 350, - "y": 540, - "wires": [ - [ - "ce28a0b5bfb0d5a1" - ] - ] - }, - { - "id": "107a030938cbfea9", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, + "x": 650, "y": 540, "wires": [ [ - "aebad788761dce4a" + "245e4341d4fb611c" ] ] }, { - "id": "ce28a0b5bfb0d5a1", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 1015, "y": 540, - "wires": [ - [] - ] - }, - { - "id": "84d6b96c8ebaac96", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadF", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 580, - "wires": [ - [ - "470b10726d298834" - ] - ] - }, - { - "id": "470b10726d298834", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "shutter ", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 16, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "700", - "step": "1", - "className": "", - "x": 310, - "y": 580, - "wires": [ - [ - "44c3947a9b92d32d" - ] - ] + "wires": [] }, { - "id": "44c3947a9b92d32d", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, + "x": 860, "y": 580, "wires": [ [] ] }, { - "id": "069bcf58b1fe44cd", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 13, - "width": 3, - "height": 1, - "name": "photocount", - "label": "Photos", - "format": "", - "layout": "row-left", - "className": "", - "x": 670, - "y": 540, - "wires": [] - }, - { - "id": "8dc7df1de59cb03a", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 15, - "width": 3, - "height": 1, - "name": "shutter", - "label": "Shutter (ms)", - "format": "", - "layout": "row-left", - "className": "", - "x": 650, - "y": 580, - "wires": [] - }, - { - "id": "cc69dba8d54a29dd", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "Crop X", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 18, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, "topic": "", - "topicType": "str", - "min": "0", - "max": "99", - "step": "1", - "className": "", - "x": 320, - "y": 620, + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, "wires": [ [ - "c2b2ab5524271123" + "4f3121f158f06a61" ] ] }, { - "id": "e3a90602605fb9e9", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "Crop Y", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 20, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "99", - "step": "1", + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", "className": "", - "x": 310, - "y": 660, + "x": 580, + "y": 260, "wires": [ - [ - "26f17a7f406df73c" - ] + [] ] }, { - "id": "9c6b48b7b4cc4e1a", + "id": "6a3d9acbe097a3d2", "type": "function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "loadI", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 190, - "y": 620, + "y": 120, "wires": [ [ - "cc69dba8d54a29dd" + "cb6ebdabaaf7d0da" ] ] }, { - "id": "c470fd0b15356206", + "id": "7ef6f1b5c67201fe", "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 190, - "y": 660, + "x": 510, + "y": 120, "wires": [ - [ - "e3a90602605fb9e9" - ] + [] ] }, { - "id": "c2b2ab5524271123", + "id": "86f7d1b2d763f6e2", "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 510, - "y": 620, + "x": 190, + "y": 160, "wires": [ - [] + [ + "c8a3fde5206ce1ae" + ] ] }, { - "id": "26f17a7f406df73c", + "id": "fd799c931139764d", "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 510, - "y": 660, - "wires": [ - [] - ] - }, - { - "id": "fecf5cff888bb570", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 17, - "width": 3, - "height": 1, - "name": "cropx", - "label": "{{msg.crop1}}", - "format": "", - "layout": "row-left", - "className": "", - "x": 690, - "y": 620, - "wires": [] - }, - { - "id": "0ee4950bd21498bd", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 19, - "width": 3, - "height": 1, - "name": "cropy", - "label": "{{msg.crop2}}", - "format": "", - "layout": "row-left", - "className": "", - "x": 690, - "y": 660, - "wires": [] - }, - { - "id": "ebbf11b55d758806", - "type": "ui_text_input", - "z": "1613373abaf77a2c", - "name": "", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 4, - "width": 3, - "height": 1, - "passthru": true, - "mode": "text", - "delay": "0", - "topic": "", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 320, - "y": 500, + "x": 190, + "y": 240, "wires": [ [ - "67385b196c517ac6" + "87be854db758a9a6" ] ] }, { - "id": "f4b3112a9ec6c487", + "id": "d5140d455122c49a", "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.payload=\"default\"\nreturn msg;", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 190, - "y": 500, + "y": 280, "wires": [ [ - "ebbf11b55d758806" + "9daea4bd57f7a00e" ] ] }, { - "id": "67385b196c517ac6", + "id": "194f3590dd4f6e3d", "type": "function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "write", - "func": "var file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 510, - "y": 500, - "wires": [ - [] - ] - }, - { - "id": "4dd7285c2b0fd79b", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "ringlight", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 12, - "width": 3, - "height": 1, - "passthru": true, - "outs": "all", - "topic": "", - "topicType": "str", - "min": 0, - "max": "3", - "step": 1, - "className": "", - "x": 320, - "y": 700, - "wires": [ - [ - "873dace18a23fdf2" - ] - ] - }, - { - "id": "873dace18a23fdf2", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "LED", - "func": "from OpenScan import ringlight\nval = msg['payload']\n\nif val == 0:\n ringlight(1,False)\n ringlight(2,False)\nelif val == 1:\n ringlight(1,False)\n ringlight(2,True)\nelif val == 2:\n ringlight(1,True)\n ringlight(2,False)\nelif val == 3:\n ringlight(1,True)\n ringlight(2,True)", - "outputs": 1, - "x": 510, - "y": 700, + "y": 240, "wires": [ [] ] }, { - "id": "9e30e33a1520fee0", + "id": "2de69452e829d780", "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "msg.payload = 0\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 190, - "y": 700, + "x": 510, + "y": 280, "wires": [ - [ - "4dd7285c2b0fd79b" - ] + [] ] }, { - "id": "7dd287f40385922f", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "start ", - "group": "7aaf184330605300", - "order": 21, - "width": 2, - "height": 1, - "passthru": false, + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", "label": "", "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-play", - "payload": "", - "payloadType": "date", - "topic": "enabled", - "topicType": "str", - "x": 150, - "y": 880, - "wires": [ - [ - "431f917c2541ae48", - "33d94a04b96a2de0", - "6d15f717d5a11002" - ] - ] - }, - { - "id": "579f2211199fd6ab", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "stop", - "group": "7aaf184330605300", - "order": 23, - "width": 2, + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-stop", - "payload": "numberofphotos", - "payloadType": "global", + "passthru": true, + "mode": "text", + "delay": "0", "topic": "", + "sendOnBlur": true, + "className": "", "topicType": "str", - "x": 810, - "y": 960, + "x": 320, + "y": 80, "wires": [ [ - "1787f08ed7070ddd", - "c1c044f3c2139f68" + "734ac3bff2df6837" ] ] }, { - "id": "431f917c2541ae48", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, create_coordinates, take_photo, save, load_bool, camera\nfrom time import sleep, strftime, time\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system\nfrom os.path import isfile\nfrom Arducam import Focuser\n\nif load_str(\"status_internal_cam\")==\"no camera found\" or load_str(\"status_internal_cam\")[:5]==\"Featu\":\n return\n\nsave('status_internal_cam','Routine-preparing')\n\nprojectname=load_str(\"routine_projectname\")\nphotocount = load_int('routine_photocount') #vorher point_count\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nangle_start = load_int('rotor_anglestart')\ncam = load_str('camera')\nSTmode = load_bool('cam_STmode')\ntt_mode = load_bool('turntable_mode')\ncam_delay_after = load_float('cam_delay_after')\ncam_delay_before = load_float('cam_delay_before')\n\nif cam == 'imx519' and STmode == True:\n focuser = Focuser('/dev/v4l-subdev1')\n stacksize = load_int('cam_stacksize')\n focus1 = load_int('cam_focus1')\n focus2 = load_int('cam_focus2')\n if focus1 > focus2:\n focus2 = focus1\n focus1 = load_int('cam_focus2') \n focusstep = int((focus2-focus1)/(stacksize - 1))\n\ncounter = 0\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp/tmp.jpg'\nzippath = basepath + 'tmp/tmp.zip'\n\nif not 'projectcode' in msg:\n projectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n msg['projectcode'] = projectcode\n msg['counter'] = -1\n if isfile(zippath):\n system('rm ' + zippath)\n sleep(1)\n\nprojectcode = msg['projectcode']\nmsg['counter'] += 1\n\nif tt_mode == False:\n coordinates = create_coordinates(angle_min,angle_max,photocount)\nelse:\n angle_start = 0\n coordinates = []\n for i in range (photocount):\n coordinates.append([0,360/photocount*(i+1)])\n\nposition_last = (angle_start , 0)\n\nzip = ZipFile(zippath, \"a\",ZIP_DEFLATED, allowZip64=True)\n\nstarttime = time()\n\nfor position in coordinates:\n counter += 1\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n\n while load_str('status_internal_cam') == 'Routine-paused':\n sleep(0.2)\n\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n\n rotor_angle = position_last[0] - position[0]\n if abs(rotor_angle) > 180:\n rotor_angle = -360 * rotor_angle/abs(rotor_angle) + rotor_angle\n\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n # tt_angle = -360 * tt_angle/abs(tt_angle) + tt_angle\n \n motorrun('rotor', rotor_angle)\n motorrun('tt', tt_angle)\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n while load_str('status_internal_cam') == 'Routine-paused':\n sleep(0.2)\n\n msg['cropx'] = load_int('cam_cropx')\n msg['cropy'] = load_int('cam_cropy')\n msg['rotation'] = load_int('cam_rotation')\n msg['filepath_in'] = 'tmp/tmp.jpg'\n msg['filepath_out'] = 'tmp/tmp.jpg'\n msg['filepath'] = 'tmp/tmp.jpg'\n\n if counter < 6:\n ETA = ''\n sleep(cam_delay_before)\n if STmode == True:\n counter2 = 0\n for focus in range (stacksize):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n while load_str('status_internal_cam') == 'Routine-paused':\n sleep(0.2)\n counter2 += 1\n save('status_internal_cam','Routine-' + str(counter) + '/' + str(photocount) + ' F' + str(counter2) + ETA)\n focuser.write(focus1 + focus * focusstep)\n take_photo('tmp/tmp.jpg')\n camera('/crop',msg)\n zip.write(temppath, projectname + '_' + str(msg['counter']) + '_' + str(counter) + '-' + str(focus) + \".jpg\")\n system('cp ' + temppath + ' ' + basepath +'tmp/preview.jpg')\n elif cam != 'external':\n save('status_internal_cam','Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n\n if cam == 'gphoto':\n camera('/gphoto_capture', msg)\n if cam in ('usb_webcam','imx219','ov5647','imx477','imx290a','imx290b','imx378','ov9281','imx519'):\n take_photo('tmp/tmp.jpg')\n camera('/crop',msg)\n \n zip.write(temppath, projectname + '_' + str(msg['counter']) + '_' + str(counter) + \".jpg\")\n system('cp ' + temppath + ' ' + basepath +'tmp/preview.jpg')\n elif cam == 'external':\n camera('external_capture')\n save('status_internal_cam','Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n\n ETA = '-ETA:'+str(int((photocount/counter - 1)*(time() - starttime)))+'/'+str(int(photocount/counter*(time() - starttime)))+'s'\n sleep(cam_delay_after)\n\n position_last = position\n\nzip.close()\n\nsave('status_internal_cam','Routine-done')\n\nmotorrun('rotor',position_last[0] - angle_start)\nmotorrun('tt',position_last[1])\n\nsave('status_internal_cam','--READY--')\n\nif load_bool('routine_secondpass')==True:\n msg['topic'] = 'Scan done'\n msg['payload'] = 'Do you want to run another pass or finish this project?'\n msg['enabled'] = False\n return msg,None\n\nreturn None,msg\n", - "outputs": 2, - "x": 300, - "y": 880, + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, "wires": [ [ - "db7eea74d3bf892b" - ], - [ - "0b8661103366f834" + "58e565fea35cb667" ] ] }, { - "id": "1787f08ed7070ddd", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "stop", - "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, - "x": 930, - "y": 960, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, "wires": [ [] ] }, { - "id": "e9b13dfd9f8d3711", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6" + "960912e90ba5b5bc", + "50eeb3e362f9027f" ], - "x": 395, - "y": 840, - "wires": [] + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] }, { - "id": "9654deebb668e012", + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", "type": "inject", - "z": "1613373abaf77a2c", - "name": "1s", + "z": "481edaf6db5a7a54", + "name": "", "props": [ { "p": "payload" @@ -1945,80 +1448,63 @@ "repeat": "", "crontab": "", "once": true, - "onceDelay": "1", + "onceDelay": "6", "topic": "", "payload": "", "payloadType": "date", - "x": 290, - "y": 1000, + "x": 170, + "y": 200, "wires": [ [ - "c1c044f3c2139f68" + "11f41a6030578ef4" ] ] }, { - "id": "8367cfa0bf5bc5df", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start routine", - "links": [ - "210ef5246d1a8790", - "84608db962fd9932", - "8689e938.dd9e38", - "f20f2dbc.0f123", - "e9b13dfd9f8d3711", - "96bdb9417e38810f", - "fb13752beddee9f2", - "bd75f33b8a57c522" - ], - "x": 55, - "y": 880, + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, "wires": [ [ - "7dd287f40385922f" + "a0156eaac7dd35e5" ] ] }, { - "id": "fb13752beddee9f2", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "8367cfa0bf5bc5df", - "b33d604c.5f1a6" - ], - "x": 895, - "y": 920, - "wires": [] - }, - { - "id": "95439678bb2df2a2", + "id": "855cbcadef1163c5", "type": "function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "enable", - "func": "msg.flag = global.get('flag')\nif (global.get('flag_pw')== true){\n return msg\n}\n", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 230, - "y": 1220, + "x": 250, + "y": 840, "wires": [ [ - "04cc2467807d2d6b", - "14f9617b5b301318" + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" ] ] }, { - "id": "948a3ae4444685f2", + "id": "1a443e20a973d2f1", "type": "change", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "flag_pw true", "rules": [ { @@ -2034,16 +1520,16 @@ "from": "", "to": "", "reg": false, - "x": 610, - "y": 1260, + "x": 630, + "y": 760, "wires": [ [] ] }, { - "id": "04cc2467807d2d6b", + "id": "d1b87196ae5373ed", "type": "change", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "flag_pw false", "rules": [ { @@ -2059,79 +1545,16 @@ "from": "", "to": "", "reg": false, - "x": 390, - "y": 1260, - "wires": [ - [] - ] - }, - { - "id": "12f1399b240830bf", - "type": "exec", - "z": "1613373abaf77a2c", - "command": " v4l2-ctl --list-formats-ext", - "addpay": "", - "append": "", - "useSpawn": "true", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "check cam", - "x": 190, - "y": 100, + "x": 430, + "y": 760, "wires": [ - [ - "6222f781629c72e7" - ], - [ - "6222f781629c72e7" - ], [] ] }, { - "id": "6222f781629c72e7", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\ncontent = '--READY--'\n\nif (msg.payload.includes('Cannot open device')){\n content = 'no camera found'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return msg\n }\n });\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 350, - "y": 100, - "wires": [ - [ - "e89c16809f8a5f1c" - ] - ] - }, - { - "id": "e978bf8c53d1f15a", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "Settings internal cam", - "info": "", - "x": 120, - "y": 40, - "wires": [] - }, - { - "id": "ccb7da246de908d1", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "preview internal cam", - "info": "", - "x": 110, - "y": 1160, - "wires": [] - }, - { - "id": "e9566588c5e40637", + "id": "03d92601c62b79d4", "type": "inject", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "4s/0.5", "props": [ { @@ -2142,1271 +1565,1127 @@ "vt": "str" } ], - "repeat": "0.5", + "repeat": "0.1", "crontab": "", "once": true, "onceDelay": "4", "topic": "Repeat", - "payload": "0.2", + "payload": "0.1", "payloadType": "str", - "x": 80, - "y": 1220, + "x": 100, + "y": 840, "wires": [ [ - "95439678bb2df2a2" + "855cbcadef1163c5" ] ] }, { - "id": "14f9617b5b301318", + "id": "41e6a4649b6afbfb", "type": "python3-function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nsleep(0.5)\n\n\nstatus = load_str('status_internal_cam')\ncam=load_str('camera')\n\n\nif msg['flag'] == False and not 'Routine' in status:\n return msg\n\nif cam == 'external':\n return\n\nmsg['payload']=\"/tmp/preview.jpg?ts=\"+str(int(time()*10))\n\nif cam == 'gphoto' and status == 'no camera found':\n if camera('/gphoto_init') == 200:\n save('status_internal_cam','--READY--')\n\nif status!=\"--READY--\":\n return msg\n\nmsg['cropx'] = load_int('cam_cropx')\nmsg['cropy'] = load_int('cam_cropy')\nmsg['rotation'] = load_int('cam_rotation')\nmsg['filepath_in'] = 'tmp/tmp.jpg'\nmsg['filepath_out'] = 'tmp/preview.jpg'\nmsg['filepath'] = 'tmp/tmp.jpg'\nmsg['preview'] = True\n\nif cam == 'gphoto':\n if camera('/gphoto_test', msg) != 200:\n save('status_internal_cam','no camera found')\n return msg\n camera('/gphoto_preview', msg)\n\nif cam in ('usb_webcam', 'imx219','ov5647','imx477','imx290a','imx290b','imx378','ov9281','imx519'):\n take_photo('tmp/tmp.jpg')\n\ncamera('/crop',msg)\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", "outputs": 1, - "x": 410, - "y": 1220, + "x": 450, + "y": 800, "wires": [ [ - "948a3ae4444685f2", - "991b587d406d0d91", - "8f5d87ce24c40b11" + "1a443e20a973d2f1", + "296636b7467fc745" ] ] }, { - "id": "991b587d406d0d91", + "id": "85a268108250ba88", "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "ce9cc9d915dc6eb6", - "name": "preview_internal", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", "order": 1, - "width": 12, - "height": 12, - "format": "
\n\n\n
\n", + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", "storeOutMessages": false, "fwdInMessages": false, "resendOnRefresh": false, "templateScope": "local", "className": "", - "x": 620, - "y": 1220, - "wires": [ - [] - ] - }, - { - "id": "1118d0965ff7c40b", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 3, - "width": 3, - "height": 1, - "name": "projectname", - "label": "Projectname", - "format": "", - "layout": "row-left", - "className": "", - "x": 670, - "y": 500, - "wires": [] - }, - { - "id": "82c8ad50ecfbc755", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 11, - "width": 3, - "height": 1, - "name": "ringlight", - "label": "Ringlight", - "format": "", - "layout": "row-left", - "className": "", - "x": 660, - "y": 700, - "wires": [] - }, - { - "id": "33d94a04b96a2de0", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 960, - "wires": [ - [ - "579f2211199fd6ab", - "c433515042ba01b5" - ] - ] - }, - { - "id": "c1c044f3c2139f68", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 810, - "y": 1000, + "x": 450, + "y": 840, "wires": [ [ - "579f2211199fd6ab", - "c433515042ba01b5" + "417f653ca0dfdcfc", + "180476141c2a44ad" ] ] }, { - "id": "9a368472a72fbc48", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "preview arducam with focus", - "info": "", - "x": 140, - "y": 1360, + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, "wires": [] }, { - "id": "8f5d87ce24c40b11", - "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "preview_arducam", - "order": 2, - "width": 10, - "height": 12, - "format": "
\n\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 630, - "y": 1300, + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, "wires": [ - [] + [ + "e864254b18c23dd1" + ] ] }, { - "id": "282efe64332193c8", + "id": "e864254b18c23dd1", "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "focus", - "func": "from OpenScan import load_str\n\nif load_str('camera') != 'imx519':\n return\n\nfrom Arducam import Focuser\n\n\nif msg['focuser'] == True:\n focuser = Focuser('/dev/v4l-subdev1')\n focuser.write(msg['focus'])\n return msg", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", "outputs": 1, - "x": 1110, - "y": 1460, + "x": 780, + "y": 840, "wires": [ [] ] }, { - "id": "64b16ef47ab6d859", - "type": "ui_switch", - "z": "1613373abaf77a2c", - "name": "MF", - "label": "", - "tooltip": "", - "group": "90223f7ddc082321", - "order": 4, - "width": 1, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "false", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "true", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 150, - "y": 1400, - "wires": [ - [ - "f017f67a8d4a3750" - ] - ] - }, - { - "id": "f017f67a8d4a3750", + "id": "180476141c2a44ad", "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "let fs = global.get('fs');\nfilepath = '/home/pi/OpenScan/settings/';\n\nvar file = 'status_internal_cam'\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data != '--READY--'){\n return\n}\n\nfile = 'cam_AFmode'\ncontent = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});\n\nglobal.set('AF',msg.payload)\nmsg.enabled = false\nif (msg.payload == false){\n msg.enabled = true\n}\nif (msg.payload == true){\n file = 'cam_focus1'\n content = String(0)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n file = 'cam_focus2'\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n \n file = 'cam_stacksize'\n content = String(2)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n global.set('focus1', 0)\n global.set('focus2', 0)\n\n}\n\n\nmsg.focus = global.get('focus')\nmsg.payload = 'down'\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 1400, + "x": 630, + "y": 880, "wires": [ [ - "5c39bd09.702d84", - "74521cf72050b515", - "b70e8c24ee011258", - "a2ff9dfd858821bc", - "ef62086d10d830fd" + "8cbdbfecbd12ef83" ] ] }, { - "id": "65145c939b6647e2", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "", - "links": [ - "960912e90ba5b5bc" - ], - "x": 55, - "y": 1400, + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, "wires": [ - [ - "64b16ef47ab6d859" - ] + [] ] }, { - "id": "5ea18678.975138", - "type": "trigger", - "z": "1613373abaf77a2c", - "name": "20ms", - "op1": "", - "op2": "0", - "op1type": "pay", - "op2type": "str", - "duration": "-20", - "extend": false, - "overrideDelay": false, - "units": "ms", - "reset": "", - "bytopic": "all", - "topic": "topic", + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", "outputs": 1, - "x": 730, - "y": 1440, + "x": 440, + "y": 720, "wires": [ [ - "fd93843e238cc9ce" + "923be3b2b25224b4" ] ] }, { - "id": "5c39bd09.702d84", + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "F+", - "order": 8, - "width": 1, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, "height": 1, - "format": " ", + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", "storeOutMessages": true, "fwdInMessages": true, - "resendOnRefresh": false, + "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 410, - "y": 1400, + "x": 310, + "y": 160, "wires": [ [ - "dcfb5cce.0431a" + "034ec9f59e50a361", + "a0156eaac7dd35e5" ] ] }, { - "id": "dcfb5cce.0431a", - "type": "switch", - "z": "1613373abaf77a2c", - "name": "", - "property": "payload", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "1", - "vt": "num" - }, - { - "t": "eq", - "v": "-1", - "vt": "num" - }, - { - "t": "eq", - "v": "up", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 3, - "x": 550, - "y": 1420, + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, "wires": [ [ - "5ea18678.975138", - "f4a41b1e7b221486" - ], - [ - "5ea18678.975138", - "f4a41b1e7b221486" - ], - [ - "8cdd0a6b.40bcd8" + "194f3590dd4f6e3d" ] ] }, { - "id": "8cdd0a6b.40bcd8", - "type": "change", - "z": "1613373abaf77a2c", - "name": "", - "rules": [ - { - "t": "set", - "p": "reset", - "pt": "msg", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 560, - "y": 1480, + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, "wires": [ [ - "5ea18678.975138", - "e9b3837b1ffb0360" + "2de69452e829d780" ] ] }, { - "id": "74521cf72050b515", + "id": "cb6ebdabaaf7d0da", "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "F-", - "order": 9, - "width": 1, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, "height": 1, - "format": " ", + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", "storeOutMessages": true, "fwdInMessages": true, - "resendOnRefresh": false, + "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 410, - "y": 1440, + "x": 320, + "y": 120, "wires": [ [ - "dcfb5cce.0431a" + "7ef6f1b5c67201fe" ] ] }, { - "id": "7219f62c9fdc6753", + "id": "82ecd3cd971cb7ea", "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 7, - "width": 2, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, "height": 1, - "name": "", - "label": "{{msg.payload}}", + "name": "projectname", + "label": "Projectname", "format": "", - "layout": "col-center", + "layout": "row-left", "className": "", - "x": 1130, - "y": 1420, + "x": 530, + "y": 40, "wires": [] }, { - "id": "b70e8c24ee011258", - "type": "function", - "z": "1613373abaf77a2c", - "name": "global", - "func": "if (msg.payload == 'down'){\n msg.enabled = false\n msg.payload = ' '\n msg.focuser = global.get('focuser')\n return msg\n}\n\n\nmsg.enabled = true\n\nsign = msg.payload\nfocus = global.get('focus')\nif (focus > 3000){\n focusstep = 5\n}\nelse if (focus <=3000 && focus > 2000){\n focusstep = 3\n}\nelse{\n focusstep = 2\n}\n\n\nfocus = focus + sign * focusstep\n\nsign = msg.payload\nif (focus > 4000){\n distance = 6\n focus = 4000\n}\nelse if (focus > 1200 && focus <= 4000){\n distance = 737086 * Math.pow(focus, -1.4096)\n}\nelse if (focus <= 1200){\n distance = 999\n if (focus <=0){\n focus = 0\n }\n}\n\n\nglobal.set('focus', focus)\nmsg.focus = focus\nmsg.distance = distance\ndistance = distance * 10\nmsg.focuser = global.get('focuser')\nmsg.payload = String(distance.toFixed(1)) + 'mm'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 970, - "y": 1440, + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, "wires": [ [ - "7219f62c9fdc6753", - "282efe64332193c8", - "704a9f89089d1f25" + "06e1e19835a9816e" ] ] }, { - "id": "f4a41b1e7b221486", - "type": "change", - "z": "1613373abaf77a2c", - "name": "focuser f", - "rules": [ - { - "t": "set", - "p": "focuser", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 740, - "y": 1400, + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, "wires": [ - [] + [ + "1fe18f3b0b52aabd" + ] ] }, { - "id": "e9b3837b1ffb0360", - "type": "change", - "z": "1613373abaf77a2c", - "name": "focuser t", - "rules": [ - { - "t": "set", - "p": "focuser", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 740, - "y": 1480, + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, "wires": [ [] ] }, { - "id": "fd93843e238cc9ce", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "10ms", - "pauseType": "delay", - "timeout": "20", - "timeoutUnits": "milliseconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, - "x": 850, - "y": 1440, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, "wires": [ [ - "b70e8c24ee011258" + "ed2974731fb8a84e" ] ] }, { - "id": "25c4138bddb77b6b", - "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "set", + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", "order": 10, "width": 2, "height": 1, - "format": "set", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", "className": "", - "x": 570, - "y": 1540, + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, "wires": [ [ - "95e1d239988b29e0" + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" ] ] }, { - "id": "95e1d239988b29e0", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "focus = global.get('focus')\nfocus1 = global.get('focus1')\nfocus2 = global.get('focus2')\nlet fs = global.get('fs');\nfilepath = '/home/pi/OpenScan/settings/';\n \nif (msg.payload == false){\n return msg\n}\n\nif (focus1 != 0 && focus2 != 0){\n global.set('focus1', 0)\n global.set('focus2', 0)\n file = 'cam_focus1'\n content = String(0)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n file = 'cam_focus2'\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n msg.distance1 = ' '\n msg.distance2 = ' '\n msg.enabled = false\n return msg\n}\n\nif (focus > 4000){\n distance = 6\n focus = 4000\n}\nelse if (focus > 1200 && focus <= 4000){\n distance = 737086 * Math.pow(focus, -1.4096)\n}\nelse if (focus <= 1200){\n distance = 999.9\n if (focus <=0){\n focus = 0\n }\n}\ndistance = distance * 10\n\nif (focus1 == 0){\n global.set('focus1', focus)\n file = 'cam_focus1'\n content = String(focus)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n global.set('distance1', distance)\n msg.distance1 = distance.toFixed(1)\n msg.distance2 = 'tbd'\n msg.enabled = false\n return msg\n}\nif (focus1 != 0 && focus2 ==0 && focus!= focus1){\n global.set('focus2', focus)\n file = 'cam_focus2'\n content = String(focus)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n global.set('distance2', distance)\n msg.distance1 = global.get('distance1').toFixed(1)\n msg.distance2 = distance.toFixed(1)\n msg.enabled = true\n return msg\n}\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 710, - "y": 1560, + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, "wires": [ [ - "7889245e91ddea4b", - "210ef5246d1a8790" + "1787f08ed7070ddd", + "c1c044f3c2139f68" ] ] }, { - "id": "7889245e91ddea4b", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 11, - "width": 2, - "height": 1, + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", "name": "", - "label": "{{msg.distance1}}", - "format": "{{msg.distance2}}", - "layout": "col-center", - "className": "", - "x": 830, - "y": 1600, + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, "wires": [] }, { - "id": "a1d29e56599da0bd", + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", "type": "link in", - "z": "1613373abaf77a2c", - "name": "focusnumber", + "z": "481edaf6db5a7a54", + "name": "start routine", "links": [ - "210ef5246d1a8790", - "2dd2503d7ab0214b", - "6b94bf2295b1b31d" + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" ], - "x": 175, - "y": 1760, + "x": 45, + "y": 1040, "wires": [ [ - "06504f47ee1744d7", - "5f8b90ef08a7d68c" + "7dd287f40385922f" ] ] }, { - "id": "210ef5246d1a8790", + "id": "fb13752beddee9f2", "type": "link out", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "", "mode": "link", "links": [ - "a1d29e56599da0bd", + "2f4c0f98.dee2", "8367cfa0bf5bc5df", - "149e2e46b9623a2d" + "b33d604c.5f1a6", + "c8b93b42c720b9cf" ], - "x": 835, - "y": 1560, + "x": 525, + "y": 1040, "wires": [] }, { - "id": "b6f37e23f2491639", - "type": "ui_switch", - "z": "1613373abaf77a2c", - "name": "Stack", - "label": "", - "tooltip": "", - "group": "90223f7ddc082321", - "order": 6, - "width": 1, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 290, - "y": 1600, - "wires": [ - [ - "2d66216fee29250c" - ] - ] - }, - { - "id": "a2ff9dfd858821bc", + "id": "33d94a04b96a2de0", "type": "function", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "enable", - "func": "msg.payload = false\nif (msg.enabled == false){\n return msg\n}\n", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 410, - "y": 1560, + "x": 290, + "y": 1100, "wires": [ [ - "25c4138bddb77b6b", - "7889245e91ddea4b", - "4cfada2de1c5bb74", - "95e1d239988b29e0" + "579f2211199fd6ab" ] ] }, { - "id": "2d66216fee29250c", + "id": "c1c044f3c2139f68", "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "file = 'cam_STmode'\nlet fs = global.get('fs');\nfilepath = '/home/pi/OpenScan/settings/';\ncontent = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});\n\nmsg.enabled = true\nglobal.set('ST',msg.payload)\nif (msg.payload == false){\n global.set('focus1',0)\n global.set('focus2',0)\n file = 'cam_focus1'\n content = String(0)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n file = 'cam_focus2'\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n \n \n msg.enabled = false\n}\nreturn msg\n", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 430, - "y": 1600, + "x": 490, + "y": 1140, "wires": [ [ - "25c4138bddb77b6b", - "7889245e91ddea4b", - "2dd2503d7ab0214b", - "4cfada2de1c5bb74" + "579f2211199fd6ab" ] ] }, { - "id": "ef62086d10d830fd", + "id": "1daf9e3a5bd5ab48", "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "msg.payload = false\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 150, - "y": 1560, + "x": 430, + "y": 1040, "wires": [ [ - "b6f37e23f2491639", - "523019d0a2c698f5" + "fb13752beddee9f2" ] ] }, { - "id": "06504f47ee1744d7", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 12, - "width": 2, - "height": 1, - "name": "", - "label": "Stacksize:", - "format": "{{msg.stacksize}}", - "layout": "row-center", - "className": "", - "x": 710, - "y": 1760, - "wires": [] - }, - { - "id": "2dd2503d7ab0214b", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "a1d29e56599da0bd" - ], - "x": 535, - "y": 1620, - "wires": [] - }, - { - "id": "21306d6402225553", + "id": "6d15f717d5a11002", "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.stacksize = msg.payload\nmsg.enabled = true\nreturn msg", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 550, - "y": 1720, + "x": 280, + "y": 1000, "wires": [ [ - "06504f47ee1744d7", - "ca184d58f7deb4b1", - "84608db962fd9932" + "e9b13dfd9f8d3711" ] ] }, { - "id": "e2f8fdd47bdd1b66", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "stacksize", - "label": " ", - "tooltip": "", - "group": "90223f7ddc082321", - "order": 13, - "width": 2, - "height": 1, - "passthru": true, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "2", - "max": "20", - "step": "1", - "className": "", - "x": 400, - "y": 1720, + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, "wires": [ [ - "21306d6402225553" + "1daf9e3a5bd5ab48", + "795c85ad4f109567" ] ] }, { - "id": "523019d0a2c698f5", + "id": "afe47a9eaae6f67f", "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 5, - "width": 1, + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, "height": 1, "name": "", - "label": "St", - "format": "", - "layout": "col-center", - "className": "", - "x": 290, - "y": 1560, - "wires": [] - }, - { - "id": "dfbfe28bac5c4221", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 3, - "width": 1, - "height": 1, - "name": "MF", - "label": "MF", - "format": "", - "layout": "col-center", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", "className": "", - "x": 150, - "y": 1440, + "x": 340, + "y": 40, "wires": [] }, { - "id": "ca184d58f7deb4b1", - "type": "function", - "z": "1613373abaf77a2c", - "name": "save", - "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.stacksize)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 690, - "y": 1720, - "wires": [ - [] - ] - }, - { - "id": "704a9f89089d1f25", + "id": "8608517d0567d63f", "type": "function", - "z": "1613373abaf77a2c", - "name": "save", - "func": "var file = 'cam_focus'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.focus)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 1110, - "y": 1500, + "x": 190, + "y": 40, "wires": [ - [] + [ + "afe47a9eaae6f67f" + ] ] }, { - "id": "5f8b90ef08a7d68c", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 1720, + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, "wires": [ [ - "e2f8fdd47bdd1b66" + "8608517d0567d63f" ] ] }, { - "id": "4cfada2de1c5bb74", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "if (msg.enabled == true){\n msg.enabled = false\n}\nelse{\n msg.enabled = true\n}\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 570, - "y": 1660, + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, "wires": [ [ - "84608db962fd9932" + "9774e7ad3b506354" ] ] }, { - "id": "84608db962fd9932", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "149e2e46b9623a2d" - ], - "x": 675, - "y": 1660, - "wires": [] - }, - { - "id": "e89c16809f8a5f1c", + "id": "9774e7ad3b506354", "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "gphoto", - "func": "\nfrom OpenScan import camera, save, load_str\n\nif load_str('camera') == 'gphoto':\n if camera('/gphoto_init') == 200:\n if camera('/gphoto_test') == 200:\n save('status_internal_cam','--READY--')\n return msg\nif load_str('camera') == 'external':\n save('status_internal_cam','--READY--')", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", "outputs": 1, - "x": 490, - "y": 100, + "x": 510, + "y": 380, "wires": [ - [] + [ + "f0af909f3e739b22" + ] ] }, { - "id": "917a194be245384a", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable projectname", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 540, + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, "wires": [ [ - "f4b3112a9ec6c487" + "fa181d22775c2ce6" ] ] }, { - "id": "65cef204b16f8741", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable shutter", - "links": [ - "2d76e5617f13cd6c", - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 580, + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, "wires": [ [ - "84d6b96c8ebaac96" + "c615034ea6b26174" ] ] }, { - "id": "2aea1727dbea76ce", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable cropx", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 620, + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, "wires": [ [ - "9c6b48b7b4cc4e1a" + "237c2135cdad86ea" ] ] }, { - "id": "4f212b44aa487945", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable cropy", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 660, + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, "wires": [ [ - "c470fd0b15356206" + "180476141c2a44ad" ] ] }, { - "id": "6d1e12f51f9af0b6", + "id": "5baf89a2682265f7", "type": "link in", - "z": "1613373abaf77a2c", - "name": "start camchk", + "z": "481edaf6db5a7a54", + "name": "enable led", "links": [ - "960912e90ba5b5bc" + "eb1a2387a1eeea76" ], - "x": 55, - "y": 100, + "x": 145, + "y": 880, "wires": [ [ - "12f1399b240830bf" + "dd7fb8791d34c751" ] ] }, { - "id": "8ebd1dcb5db156ed", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 2, - "width": 6, - "height": 1, - "name": "", - "label": "Current Status:", - "format": " {{msg.payload}} ", - "layout": "row-spread", - "className": "", - "x": 320, - "y": 160, - "wires": [] - }, - { - "id": "94a7aec739f9266b", + "id": "6a26e8a7253d708c", "type": "function", - "z": "1613373abaf77a2c", - "name": "loadS", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 160, + "x": 830, + "y": 40, "wires": [ [ - "8ebd1dcb5db156ed" + "39c744466a21735e" ] ] }, { - "id": "2415272f42ce468c", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start status", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 55, - "y": 160, + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, "wires": [ [ - "94a7aec739f9266b" + "61aab497fa50898e" ] ] }, { - "id": "a1e14624058e74cd", + "id": "9fd259de91de1da1", "type": "link in", - "z": "1613373abaf77a2c", + "z": "481edaf6db5a7a54", "name": "start routine settings", "links": [ - "960912e90ba5b5bc" + "960912e90ba5b5bc", + "50eeb3e362f9027f" ], - "x": 55, - "y": 500, + "x": 735, + "y": 40, "wires": [ [ - "f4b3112a9ec6c487", - "107a030938cbfea9", - "84d6b96c8ebaac96", - "9c6b48b7b4cc4e1a", - "c470fd0b15356206", - "9e30e33a1520fee0", - "79ecb889f7113405" + "6a26e8a7253d708c", + "35ad7e55833836c1" ] ] }, { - "id": "1daf9e3a5bd5ab48", + "id": "fa181d22775c2ce6", "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "global.set('flag_pw', true)\nglobal.set('flag', true)\nmsg.enabled = true\nreturn msg\n", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 810, - "y": 920, + "x": 1150, + "y": 40, "wires": [ [ - "fb13752beddee9f2" + "ae5ee8787145906d" ] ] }, { - "id": "6d15f717d5a11002", + "id": "c615034ea6b26174", "type": "function", - "z": "1613373abaf77a2c", - "name": "disable", - "func": "msg.enabled = false\nreturn msg\n", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 300, - "y": 840, + "x": 1150, + "y": 80, "wires": [ [ - "e9b13dfd9f8d3711" + "ae5ee8787145906d" ] ] }, { - "id": "d14bbbb446d45e39", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "preview", - "links": [ - "f20da2fc4978b7bf" - ], - "x": 135, - "y": 1260, + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, "wires": [ - [ - "95439678bb2df2a2" - ] + [] ] }, { - "id": "db7eea74d3bf892b", - "type": "ui_toast", - "z": "1613373abaf77a2c", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Finish", - "cancel": "2nd pass", - "raw": false, - "className": "", - "topic": "", + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", "name": "", - "x": 510, - "y": 880, + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, "wires": [ [ - "0b8661103366f834" + "710fc2dbb5ef0167" ] ] }, { - "id": "0b8661103366f834", + "id": "710fc2dbb5ef0167", "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "continue", - "func": "from os import system\nfrom os.path import isfile\n\n\nif msg['payload'] == '2nd pass':\n msg['enabled'] = True\n return msg,None\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp/tmp.jpg'\nzippath = basepath + 'tmp/tmp.zip'\nprojectcode = msg['projectcode']\n\nsystem('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')\n\nmsg['path'] = basepath + 'scans/' + projectcode + '.zip'\n\nif isfile(zippath):\n system('rm ' + zippath)\n\nreturn None, msg", - "outputs": 2, - "x": 660, - "y": 920, + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, "wires": [ [ - "431f917c2541ae48", - "579f2211199fd6ab", - "c433515042ba01b5" - ], - [ - "1daf9e3a5bd5ab48", - "579f2211199fd6ab", - "c433515042ba01b5" + "78cfe60013a1bea4" ] ] }, { - "id": "79ecb889f7113405", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "inactive", - "func": "from requests import get\nfrom OpenScan import load_int\n\ntimeout = load_int('timeout_ringlight')\n\nmsg['cmd'] = 'get'\n\ntry:\n flask = 'http://127.0.0.1:1312/ping'\n r = get(flask, params=msg)\n\n idle = float(r.text.split(\":\")[1].split('}')[0])\n\n msg['payload'] = idle\n\n if idle > timeout:\n return msg,msg\nexcept:\n pass\n\nreturn None,msg", - "outputs": 2, - "x": 200, - "y": 740, + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, "wires": [ [ - "9e30e33a1520fee0" - ], - [ - "8d7e04531c34f349" + "5e83b653850fa16e" ] ] }, { - "id": "8d7e04531c34f349", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "", - "pauseType": "delay", - "timeout": "30", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, - "x": 200, - "y": 780, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, "wires": [ [ - "79ecb889f7113405" + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" ] ] }, { - "id": "c433515042ba01b5", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "pause", - "group": "7aaf184330605300", - "order": 22, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-pause", - "payload": " ", - "payloadType": "str", - "topic": "Scan paused", - "topicType": "str", - "x": 810, - "y": 1040, + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, "wires": [ [ - "63db399d8ac2acb6" + "ed2974731fb8a84e" ] ] }, { - "id": "63db399d8ac2acb6", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "pause", - "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nif status == 'Routine-paused':\n save('status_internal_cam', 'Routine-continue')\nelse:\n save('status_internal_cam', 'Routine-paused')", - "outputs": 1, - "x": 930, - "y": 1040, + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, "wires": [ - [] + [ + "ed2974731fb8a84e" + ] ] }, { - "id": "44c598049cd533fd", + "id": "c8b93b42c720b9cf", "type": "link in", - "z": "1613373abaf77a2c", - "name": "crop", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", "links": [ - "f358de1e64b491bb" + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" ], - "x": 595, - "y": 640, + "x": 85, + "y": 380, "wires": [ [ - "fecf5cff888bb570", - "0ee4950bd21498bd" + "78cfe60013a1bea4" ] ] }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, { "id": "ea54fcc2.cfcc2", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "get dirs", "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", "outputs": 1, @@ -3414,120 +2693,37 @@ "y": 180, "wires": [ [ - "b9a3a0f9.bcbea", - "f3662f8c7d3d7a2d" + "f3662f8c7d3d7a2d", + "01e4783e148c6698" ] ] }, { "id": "2f4c0f98.dee2", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "filelist", "links": [ + "50eeb3e362f9027f", "960912e90ba5b5bc", "a4f09e25.02569", "ed35109311335099", "fb13752beddee9f2" ], "x": 355, - "y": 140, + "y": 220, "wires": [ [ "ea54fcc2.cfcc2" ] ] }, - { - "id": "b9a3a0f9.bcbea", - "type": "ui_table", - "z": "4981d84ef1a366d1", - "group": "b5fdd57b.15eda8", - "name": "", - "order": 1, - "width": 13, - "height": 7, - "columns": [ - { - "field": "Date", - "title": "Date", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Name", - "title": "Name", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Photos", - "title": "Photos", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Duration", - "title": "ΔT", - "width": "60", - "align": "left", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Size", - "title": "Size", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Status", - "title": "Status", - "width": "140", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - } - ], - "outputs": 1, - "cts": true, - "x": 610, - "y": 180, - "wires": [ - [ - "50710948.71c308", - "4082b136.dae18", - "834046a4.647938", - "0c387c0291d6c131" - ] - ] - }, { "id": "952ce286.4ffd4", "type": "ui_text", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", - "order": 3, + "order": 4, "width": 6, "height": 1, "name": "Status", @@ -3542,7 +2738,7 @@ { "id": "d4383424.7807c8", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "upload", "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", "outputs": 2, @@ -3561,7 +2757,7 @@ { "id": "50710948.71c308", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "set", "rules": [ { @@ -3589,9 +2785,9 @@ { "id": "834046a4.647938", "type": "ui_text", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", - "order": 4, + "order": 5, "width": 6, "height": 1, "name": "Set", @@ -3606,7 +2802,7 @@ { "id": "9a132ab1.b21658", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "flag.true", "rules": [ { @@ -3633,9 +2829,9 @@ { "id": "3c67e97b.9d19a6", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "enable", - "func": "if (global.get('flag') === false){\n msg.enabled = false\n msg.color=\"white\"\n}\nelse{\n msg.enabled = true\n msg.color=\"red\"\n \n}\n\nreturn msg", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -3656,7 +2852,7 @@ { "id": "bfc01f26.c32cf", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "flag.false", "rules": [ { @@ -3683,7 +2879,7 @@ { "id": "b33d604c.5f1a6", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "enable cloud", "links": [ "4082b136.dae18", @@ -3704,7 +2900,7 @@ { "id": "f6bd1a04.470838", "type": "change", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "set", "rules": [ { @@ -3731,7 +2927,7 @@ { "id": "4082b136.dae18", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ "b33d604c.5f1a6", @@ -3744,7 +2940,7 @@ { "id": "f20f2dbc.0f123", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -3759,7 +2955,7 @@ { "id": "8689e938.dd9e38", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -3774,7 +2970,7 @@ { "id": "15de0ebb.616d61", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -3797,7 +2993,7 @@ { "id": "a7d89487.ee8858", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "del", "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", "outputs": 1, @@ -3812,16 +3008,11 @@ { "id": "a4f09e25.02569", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", + "mode": "link", "links": [ - "2f4c0f98.dee2", - "c20357dd.374108", - "e9aab326.a6896", - "edd22cc7.befe1", - "19b81967.49db87", - "8ee1b3bb.7b0b3", - "d5246b3cc796afc6" + "2f4c0f98.dee2" ], "x": 775, "y": 360, @@ -3830,7 +3021,7 @@ { "id": "7a93d1e18254685c", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -3844,7 +3035,7 @@ { "id": "4d99c601c9881680", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "refresh", "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", "outputs": 2, @@ -3863,7 +3054,7 @@ { "id": "372e95797a3f2f3b", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "limit :)", "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", "outputs": 1, @@ -3878,7 +3069,7 @@ { "id": "573edbfdb7500ddc", "type": "delay", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "pauseType": "rate", "timeout": "5", @@ -3903,7 +3094,7 @@ { "id": "dacb1f078b624e10", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -3926,7 +3117,7 @@ { "id": "92c98e6ce7cd25f9", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ "7a93d1e18254685c", @@ -3943,7 +3134,7 @@ { "id": "3d16b3789632784d", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -3964,7 +3155,7 @@ { "id": "6434e713f088012b", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -3985,9 +3176,9 @@ { "id": "c8d65cc7c2ff7c36", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if os.path.isdir(dir + i):\n shutil.rmtree(dir + i)\n else:\n os.remove(dir + i)\n\nreturn msg", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", "outputs": 1, "x": 690, "y": 340, @@ -4000,7 +3191,7 @@ { "id": "f4e9a4bd79b4221f", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "msg", "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", "outputs": 1, @@ -4019,7 +3210,7 @@ { "id": "2806bf08ea21216d", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "msg", "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", "outputs": 1, @@ -4038,7 +3229,7 @@ { "id": "61990987acd0f263", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ "6c6ef2255a7d39e5" @@ -4054,10 +3245,10 @@ { "id": "e8e488a6dd5d0b33", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "Download", - "order": 6, + "order": 8, "width": 3, "height": 1, "format": "\n
Download\n
\n", @@ -4075,7 +3266,7 @@ { "id": "0c387c0291d6c131", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "msg", "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", "outputs": 1, @@ -4094,10 +3285,11 @@ { "id": "e5f38b4a07a5e278", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "links": [ - "960912e90ba5b5bc" + "960912e90ba5b5bc", + "50eeb3e362f9027f" ], "x": 655, "y": 220, @@ -4110,7 +3302,7 @@ { "id": "e434ef42bd6b92e8", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "upload2", "order": 7, @@ -4126,19 +3318,18 @@ "y": 460, "wires": [ [ - "f6bd1a04.470838", - "bfc01f26.c32cf" + "f6bd1a04.470838" ] ] }, { "id": "c46e10b9c201913e", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "refresh", - "order": 1, - "width": 3, + "order": 2, + "width": 4, "height": 1, "format": "refresh{{msg.text}}", "storeOutMessages": false, @@ -4158,10 +3349,10 @@ { "id": "d5d840183025d91b", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "del set", - "order": 9, + "order": 11, "width": 2, "height": 1, "format": "delete set", @@ -4181,7 +3372,7 @@ { "id": "ab9e90ab5a53a0dd", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "del ", "order": 10, @@ -4204,10 +3395,10 @@ { "id": "478994f671a3907d", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "combine", - "order": 8, + "order": 9, "width": 2, "height": 1, "format": "combine", @@ -4227,7 +3418,7 @@ { "id": "189c1eed09624a7b", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", "outputs": 1, @@ -4246,7 +3437,7 @@ { "id": "51bfd0fb7b1d292e", "type": "function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", "outputs": 1, @@ -4263,7 +3454,7 @@ { "id": "da325be8e74179be", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", "outputs": 1, @@ -4278,7 +3469,7 @@ { "id": "ed35109311335099", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ @@ -4292,7 +3483,7 @@ { "id": "1493398979a63775", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4315,7 +3506,7 @@ { "id": "ada1b6f7cccc9344", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "mode": "link", "links": [ @@ -4328,7 +3519,7 @@ { "id": "6dd356510c446cf4", "type": "link in", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "combine", "links": [ "ada1b6f7cccc9344" @@ -4344,11 +3535,12 @@ { "id": "b42e061fb1f1f3d7", "type": "link out", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "mode": "link", "links": [ - "397ab7f44b893c89" + "397ab7f44b893c89", + "3876d5cbd248592b" ], "x": 435, "y": 140, @@ -4357,7 +3549,7 @@ { "id": "b99505440832439f", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "diskspace", "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", "outputs": 1, @@ -4372,7 +3564,7 @@ { "id": "92047434f8e9f927", "type": "ui_toast", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4393,7 +3585,7 @@ { "id": "f3662f8c7d3d7a2d", "type": "delay", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "", "pauseType": "rate", "timeout": "5", @@ -4418,7 +3610,7 @@ { "id": "51579603bce21e98", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "read", "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", "outputs": 1, @@ -4433,10 +3625,10 @@ { "id": "9a5baae623355f9d", "type": "ui_template", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "group": "db43d646.2074c8", "name": "preview", - "order": 5, + "order": 6, "width": 6, "height": 6, "format": "
\n\n\n
\n", @@ -4454,7 +3646,7 @@ { "id": "85839a17fb7b58b9", "type": "python3-function", - "z": "4981d84ef1a366d1", + "z": "80a3942785a26c29", "name": "preview", "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", "outputs": 1, @@ -4467,40 +3659,97 @@ ] }, { - "id": "45058bfcf047e8cc", - "type": "inject", - "z": "4981d84ef1a366d1", + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", "name": "", - "props": [ + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, { - "p": "payload" + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } }, { - "p": "topic", - "vt": "str" + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } } ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 100, - "y": 120, + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, "wires": [ - [] + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] ] }, { - "id": "40dee936a9abac0d", + "id": "cb3437ec113e1b6f", "type": "ui_switch", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "label": "SSH", "tooltip": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 3, "width": 6, "height": 1, @@ -4520,21 +3769,21 @@ "animate": false, "className": "", "x": 390, - "y": 340, + "y": 360, "wires": [ [ - "dc354c54078ca607" + "c24f61b87e3226dd" ] ] }, { - "id": "4fd9bb53fdb51a25", + "id": "60fd0adce1cfeb82", "type": "ui_switch", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "label": "Samba", "tooltip": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 4, "width": 6, "height": 1, @@ -4554,32 +3803,32 @@ "animate": false, "className": "", "x": 400, - "y": 380, + "y": 400, "wires": [ [ - "b0aa8ffae5a3578a" + "441d3ef525e901da" ] ] }, { - "id": "dc354c54078ca607", + "id": "c24f61b87e3226dd", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ssh", "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", "outputs": 1, "x": 530, - "y": 340, + "y": 360, "wires": [ [] ] }, { - "id": "52858b4eceacc902", + "id": "c013e836e759a085", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 2, "width": 6, "height": 1, @@ -4595,45 +3844,17 @@ "topic": "topic", "topicType": "msg", "x": 120, - "y": 300, - "wires": [ - [ - "f99ec8781a33ec7d" - ] - ] - }, - { - "id": "595153429adef33b", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Wifi", - "group": "0fe66c9190b8a87c", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Network Settings

Hostname

The device can be accessed through any browser in the same network. Therefore, you can either enter the device's IP address or the given hostname. The standard name is 'openscan' but it is highly recommended to change the name, when using multiple devices (e.g. 'openscan1', 'openscan2' ...)

Select Wifi

After booting, the device will automatically search for available wireless networks and create a list. You can connect to a given network by entering the wifi password and country code. To find the right two-character country code, see the following list: ISO 3166 Country Code on Wikipedia

Search Wifi

You can manually refresh the list of available networks by pressing this button.

Reset Wifi

Delete the list of known wireless networks (and passwords) and reset the default. After this step, you will either need to use Ethernet or a modified wpa_supplicant.conf file. (see glennklockwood.com for more details about the wpa_supplicant.conf file, which has to be manually created and placed into the /boot/ directory of the sd-card)

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 590, - "y": 120, + "y": 320, "wires": [ [ - "f304680180a23479" + "b78346ca3ce70c68" ] ] }, { - "id": "7dc39bd847d16ded", + "id": "f0d8dbcca76a1926", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -4646,19 +3867,19 @@ "topic": "", "name": "", "x": 410, - "y": 300, + "y": 320, "wires": [ [ - "5f849178998d9082" + "e95b86cbac1b03b9" ] ] }, { - "id": "02858034e17b827f", + "id": "34374044c0030625", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "General", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 1, "width": 6, "height": 1, @@ -4674,19 +3895,19 @@ "topic": "topic", "topicType": "msg", "x": 740, - "y": 240, + "y": 220, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "675d4933a44ae6b5", + "id": "b2b6bf23c9989133", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Pinout", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 1, "width": 6, "height": 1, @@ -4702,219 +3923,32 @@ "topic": "topic", "topicType": "msg", "x": 430, - "y": 200, + "y": 220, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "b0aa8ffae5a3578a", + "id": "441d3ef525e901da", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "smb", "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", "outputs": 1, "x": 530, - "y": 380, - "wires": [ - [] - ] - }, - { - "id": "cc3cb10f2ea3f8b8", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "blink Light1", - "func": "import RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nfrom OpenScan import ringlight\nfrom time import sleep\n\ndelay = 0.1\nringlight(2,False)\n\nfor i in range (5):\n ringlight(1,True)\n sleep(delay)\n ringlight(1,False)\n sleep(delay)", - "outputs": 1, - "x": 290, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "d114f4d4d7f31981", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "reboot", - "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", - "outputs": 1, - "x": 270, - "y": 720, + "y": 400, "wires": [ [] ] }, { - "id": "79181ad3b56d5c62", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "4fe6b4c0ade0938a", - "order": 7, - "width": 2, - "height": 1, - "name": "", - "label": "Model", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 620, - "wires": [] - }, - { - "id": "4d81bd138733c410", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "4fe6b4c0ade0938a", - "order": 9, - "width": 2, - "height": 1, - "name": "", - "label": "Camera", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 840, - "y": 420, - "wires": [] - }, - { - "id": "80b579a4220e5c23", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "model", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "4fe6b4c0ade0938a", - "order": 8, - "width": 4, - "height": 1, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Please Select", - "value": "None", - "type": "str" - }, - { - "label": "OpenScan Mini", - "value": "OSMini", - "type": "str" - }, - { - "label": "OpenScan Classic", - "value": "OSClassic", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 390, - "y": 620, - "wires": [ - [ - "896242c5a7e50fa7" - ] - ] - }, - { - "id": "a2c1dba3e67be015", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "Camera", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "4fe6b4c0ade0938a", - "order": 10, - "width": 4, - "height": 1, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Pi Cam v1 - 5mp", - "value": "ov5647", - "type": "str" - }, - { - "label": "Pi Cam v2 - 8mp", - "value": "imx219", - "type": "str" - }, - { - "label": "Pi Cam HQ - 12.3mp", - "value": "imx477", - "type": "str" - }, - { - "label": "Arducam IMX519 - 16mp", - "value": "imx519", - "type": "str" - }, - { - "label": "IMX290 a", - "value": "imx290a", - "type": "str" - }, - { - "label": "IMX290 b", - "value": "imx290b", - "type": "str" - }, - { - "label": "IMX378", - "value": "imx378", - "type": "str" - }, - { - "label": "OV9281", - "value": "ov9281", - "type": "str" - }, - { - "label": "DSLR (gphoto)", - "value": "gphoto", - "type": "str" - }, - { - "label": "USB Webcam", - "value": "usb_webcam", - "type": "str" - }, - { - "label": "External Camera", - "value": "external", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 400, - "y": 420, - "wires": [ - [ - "4058a31e942e8f95", - "6d68cccec646e0a0" - ] - ] - }, - { - "id": "9cf5d56263caada7", + "id": "3256bab150113a48", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Motor", - "group": "d49a6dfd7fb17096", + "group": "7a3279eea439bcdd", "order": 1, "width": 6, "height": 1, @@ -4930,19 +3964,19 @@ "topic": "topic", "topicType": "msg", "x": 430, - "y": 120, + "y": 140, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "72238e6a01d1152c", + "id": "7a186669a17daa71", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "camera", - "group": "93aadb71dee6d977", + "group": "d324f0b852c2df0a", "order": 1, "width": 6, "height": 1, @@ -4958,71 +3992,41 @@ "topic": "topic", "topicType": "msg", "x": 420, - "y": 160, + "y": 180, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "15a0a2f431ce55c3", + "id": "edac7dd292e7e486", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "General Settings", "info": "", "x": 120, - "y": 260, - "wires": [] - }, - { - "id": "87a403b9a09aa38d", - "type": "comment", - "z": "017bd4e4a428bee5", - "name": "Network", - "info": "", - "x": 100, - "y": 880, + "y": 280, "wires": [] }, { - "id": "896242c5a7e50fa7", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "model", - "func": "from OpenScan import load_str, save\n\nstate = msg['payload']\nmsg['state'] = state\n\nif state != load_str('model'):\n save('model', state)\n if state == 'OSMini':\n save('rotor_stepsperrotation',48000)\n save('cam_rotation',90)\n save('rotor_anglemin',-70)\n save('rotor_anglemax',20)\n \n\n if state == 'OSClassic':\n save('rotor_stepsperrotation',17067)\n save('cam_rotation',0)\n save('rotor_anglemin',-30)\n save('rotor_anglemax',30)\n\nif state == \"OSMini\":\n msg['crop2'] = 'Crop X (%)'\n msg['crop1'] = 'Crop Y (%)'\nelif state == \"OSClassic\":\n msg['crop1'] = 'Crop X (%)'\n msg['crop2'] = 'Crop Y (%)'\n\nreturn msg", - "outputs": 1, - "x": 530, - "y": 620, - "wires": [ - [ - "f358de1e64b491bb" - ] - ] - }, - { - "id": "4058a31e942e8f95", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "camera", - "func": "from OpenScan import load_str, save\nfrom json import load\nstate = msg['payload']\nstate_old = load_str('camera')\n\nif state_old != state:\n save('camera',state)\n return msg", - "outputs": 1, - "x": 540, - "y": 500, - "wires": [ - [ - "34b685aff2080d31" - ] - ] - }, - { - "id": "c833f6243a059d83", + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", "type": "ui_switch", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "more sets", "label": "Advanced Settings", "tooltip": "", - "group": "4fe6b4c0ade0938a", + "group": "4390b2ebcbbe104c", "order": 5, "width": 6, "height": 1, @@ -5042,71 +4046,47 @@ "animate": false, "className": "", "x": 400, - "y": 660, + "y": 480, "wires": [ [ - "8be8015931c663cc" + "f06a7bcad524e9f9" ] ] }, { - "id": "15fd1c9e5610cb85", + "id": "29745a36fc157f3f", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "more sets", "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", "outputs": 2, "x": 820, - "y": 660, - "wires": [ - [ - "62cd775a1c02dac8" - ], - [ - "c833f6243a059d83" - ] - ] - }, - { - "id": "74c5c7cd2681045b", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "load camera&model", - "func": "from OpenScan import load_str, load_bool\n\nmodel = load_str('model')\ncamera = load_str('camera')\nupdate = load_bool('updateable')\nmsg['model'] = model\nmsg['camera'] = camera\nmsg2 = {}\nmsg3 = {}\nmsg4 = {}\n\nif camera in ('imx219','ov5647','imx477','imx290a','imx290b','imx378','ov9281','gphoto'):\n msg['payload'] = {\"group\":{\"hide\":[\"Scan_Arducam\"],\"show\":[\"Scan_Settings\",\"Scan_Picamera\"]}}\nelif camera in ('imx519'):\n msg['payload'] = {\"group\":{\"hide\":[\"Scan_Picamera\"],\"show\":[\"Scan_Settings\",\"Scan_Arducam\"]}}\nelif camera in ('external'):\n msg['payload'] = {\"group\":{\"hide\":[\"Scan_Arducam\",\"Scan_Picamera\"],\"show\":[\"Scan_Settings\"]}}\n\n\nif model == 'None' or model == '' or camera == 'None' or camera == '':\n msg2['payload']={\"tabs\": {\"hide\": [\"Scan\", \"Files&Cloud\",\"Settings\",\"Update & Info\"]}}\n msg3['payload'] = {\"group\":{\"hide\":[\"OpenScan_Home\"],\"show\":[\"OpenScan_Initialize\"]}}\nelse:\n msg2['payload']={\"tabs\": {\"show\": [\"Scan\", \"Files&Cloud\",\"Settings\",\"Update & Info\"]},\"hide\":{}}\n msg3['payload'] = {\"group\":{\"show\":[\"OpenScan_Home\"],\"hide\":[\"OpenScan_Initialize\"]}}\n\nif update == True:\n msg4['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg4['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\nreturn msg,msg2,msg3,msg4", - "outputs": 4, - "x": 340, - "y": 40, + "y": 480, "wires": [ [ - "b4db790aad28ba39" - ], - [ - "b4db790aad28ba39" - ], - [ - "b4db790aad28ba39" + "8750ad979e9ea246" ], [ - "b4db790aad28ba39" + "f6d6cc35679ede63" ] ] }, { - "id": "b4db790aad28ba39", + "id": "bf23328f9fb11b22", "type": "ui_ui_control", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "change visibility", "events": "all", "x": 600, - "y": 40, + "y": 60, "wires": [ [] ] }, { - "id": "eb8ccf2786ea3d63", + "id": "b37be1d222bc70c9", "type": "inject", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "1s_repeater", "props": [ { @@ -5125,60 +4105,62 @@ "payload": "", "payloadType": "date", "x": 150, - "y": 40, + "y": 60, "wires": [ [ - "74c5c7cd2681045b", - "9b756a1f9b0e7317" + "89eedf29b404f750" ] ] }, { - "id": "9b756a1f9b0e7317", + "id": "89eedf29b404f750", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "load advanced", - "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\nreturn msg", - "outputs": 1, - "x": 320, - "y": 80, + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, "wires": [ [ - "b4db790aad28ba39" + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" ] ] }, { - "id": "ca4afadb5b21751f", + "id": "2050de5d9e02f69f", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Info Texts", "info": "", "x": 100, - "y": 120, + "y": 140, "wires": [] }, { - "id": "f393400.d87dcc", + "id": "ded3086945a6d4b5", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "check ip address", "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", "outputs": 1, - "x": 410, - "y": 1060, + "x": 250, + "y": 940, "wires": [ [ - "bb789eed.9f73c" + "3cfe464506f46ecd" ] ] }, { - "id": "bb789eed.9f73c", + "id": "3cfe464506f46ecd", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "0fe66c9190b8a87c", - "order": 2, + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, "width": 0, "height": 0, "name": "", @@ -5186,299 +4168,26 @@ "format": "{{msg.ip}}", "layout": "row-spread", "className": "", - "x": 590, - "y": 1060, + "x": 430, + "y": 940, "wires": [] }, { - "id": "2a0f9919.4c9a86", + "id": "bd206ad109831e6a", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "OpenScanCloud", "info": "", "x": 120, - "y": 1240, + "y": 1260, "wires": [] }, { - "id": "27c6b221c90ed9e1", - "type": "exec", - "z": "017bd4e4a428bee5", - "command": "iwlist wlan0 scan | grep ESSID | sed 's/ESSID://g;s/\"//g;s/^ *//;s/ *$//'", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "scan", - "x": 250, - "y": 1040, - "wires": [ - [ - "b05cf92302a5c112", - "f393400.d87dcc" - ], - [ - "e9677b85856b5873" - ], - [] - ] - }, - { - "id": "b05cf92302a5c112", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "WIFI", - "func": "msg['options']=[]\n\nfor i in msg['payload'].split('\\n'):\n if i not in msg['options'] and i!=\"\":\n msg['options'].append(i)\n \nif len(msg['options']) != 0:\n msg['enabled']=True\n\nreturn msg", - "outputs": 1, - "x": 370, - "y": 1020, - "wires": [ - [ - "59c9f67283ba1709" - ] - ] - }, - { - "id": "da5ddaf4cc25b8c8", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "search", - "group": "0fe66c9190b8a87c", - "order": 4, - "width": 3, - "height": 1, - "passthru": false, - "label": "Search Wifi", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "true", - "payloadType": "bool", - "topic": "", - "topicType": "str", - "x": 90, - "y": 980, - "wires": [ - [ - "27c6b221c90ed9e1", - "51521bc6eb44cde5" - ] - ] - }, - { - "id": "59c9f67283ba1709", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "", - "label": "", - "tooltip": "", - "place": "Select Wifi", - "group": "0fe66c9190b8a87c", - "order": 3, - "width": 6, - "height": 1, - "passthru": true, - "multiple": false, - "options": [], - "payload": "", - "topic": "", - "topicType": "str", - "className": "", - "x": 520, - "y": 980, - "wires": [ - [ - "2bb52656f9554dab" - ] - ] - }, - { - "id": "b2d7d6a730f7dca6", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Reset Wifi", - "group": "0fe66c9190b8a87c", - "order": 5, - "width": 3, - "height": 1, - "passthru": false, - "label": "Reset Wifi", - "tooltip": "", - "color": "red", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "Delete all prior wifi connections? (You will need to reconnect to the OpenScan device by Ethernet or manually modify the wpa_supplicant.conf)", - "payloadType": "str", - "topic": "", - "topicType": "str", - "x": 110, - "y": 1140, - "wires": [ - [ - "78985ac6d3bcdf60" - ] - ] - }, - { - "id": "c3b8faac9ebb2c80", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "Reset Wifi", - "func": "from time import sleep\n\nif msg['payload']!=\"Yes\":\n return\n\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\nsleep(3)\nos.system('systemctl restart nodered')\nreturn msg", - "outputs": 1, - "x": 440, - "y": 1140, - "wires": [ - [] - ] - }, - { - "id": "78985ac6d3bcdf60", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 270, - "y": 1140, - "wires": [ - [ - "c3b8faac9ebb2c80" - ] - ] - }, - { - "id": "4f7f49b12c2d2572", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "add Wifi", - "func": "from time import sleep\nsleep(0.1)\n\nos.system('wpa_cli -i wlan0 reconfigure')\n\nreturn msg", - "outputs": 1, - "x": 1320, - "y": 1000, - "wires": [ - [] - ] - }, - { - "id": "ebcc98685059b9d4", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "password", - "x": 780, - "y": 980, - "wires": [ - [ - "68204a14528ab842" - ] - ] - }, - { - "id": "68204a14528ab842", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "if msg['payload'] == 'Cancel':\n return\n\nmsg['password'] = msg['payload']\nmsg['payload']='Enter country code (ISO 3166-1 alpha-2, see: Wikipedia)'\n\n\nreturn msg", - "outputs": 1, - "x": 910, - "y": 980, - "wires": [ - [ - "852edf901bdec9c5" - ] - ] - }, - { - "id": "852edf901bdec9c5", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Save", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "country", - "x": 1040, - "y": 980, - "wires": [ - [ - "1b09d634e3d9357b" - ] - ] - }, - { - "id": "1b09d634e3d9357b", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "modWPA", - "func": "if msg['payload'] == 'Cancel':\n return\n\nif len(msg['payload'])!=2:\n msg['payload'] = 'invalid country code'\n return msg,None\n\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\ntemp_dir = '/home/pi/OpenScan/tmp/wpa'\n\ncode = msg['payload'].upper()\nssid = msg['ssid']\npassword = msg['password']\n\nif len(code) != 2:\n msg['topic'] = 'ERROR'\n msg['payload'] = 'invalid country code (see ISO 3166-1 alpha-2)'\n return msg\n\nwith open(wpa_dir, 'r') as file:\n for i in file.readlines():\n if 'country=' in i:\n code_old=i.split('country=')[1][0:2]\n break\n\nwith open(wpa_dir, 'r') as file:\n wpa = file.read()\n if ssid in wpa:\n msg['topic'] = 'ERROR'\n msg['payload'] = 'Network already exists! If you have trouble connecting, please consider resetting the saved Wifi connections.'\n return msg\n wpa=wpa.replace('country=' + code_old, 'country=' + code)\n wpa=wpa + '\\nnetwork={\\n priority=10\\n ssid=\"'+ssid+'\"\\n psk=\"'+password+'\"\\n}\\n'\n\nwith open(temp_dir,'w+') as file:\n file.write(wpa)\nos.system('mv '+temp_dir + ' ' + wpa_dir)\n\nmsg['topic'] = 'Updating Wifi'\nmsg['payload'] = 'reconnecting might take a moment'\nreturn msg,msg\n", - "outputs": 2, - "x": 1180, - "y": 980, - "wires": [ - [ - "03732a7d3b0c95aa" - ], - [ - "4f7f49b12c2d2572" - ] - ] - }, - { - "id": "03732a7d3b0c95aa", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1330, - "y": 960, - "wires": [ - [] - ] - }, - { - "id": "e97d17c6590138e2", + "id": "b70a9a665c1e4d36", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Cloud-settings", - "group": "3b4bd36726be16d5", + "group": "12b719cba49817c9", "order": 1, "width": 6, "height": 1, @@ -5493,19 +4202,19 @@ "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 620, - "y": 160, + "x": 740, + "y": 260, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "f7bf47e3eec6d736", + "id": "c9f0566601a3e130", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", "order": 4, "width": 0, "height": 0, @@ -5515,14 +4224,14 @@ "layout": "row-spread", "className": "", "x": 410, - "y": 1380, + "y": 1400, "wires": [] }, { - "id": "b52d91c628b151a4", + "id": "9bd86d27ea499a2a", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", "order": 5, "width": 0, "height": 0, @@ -5532,14 +4241,14 @@ "layout": "row-spread", "className": "", "x": 390, - "y": 1420, + "y": 1440, "wires": [] }, { - "id": "1969c709ef2fd1d5", + "id": "2c37f7030810d234", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", "order": 3, "width": 0, "height": 0, @@ -5549,32 +4258,32 @@ "layout": "row-spread", "className": "", "x": 370, - "y": 1460, + "y": 1480, "wires": [] }, { - "id": "88e92b621d2a3394", + "id": "f40286c18afd4501", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "save", "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", "outputs": 2, "x": 750, - "y": 1320, + "y": 1340, "wires": [ [ - "76acd48a511a5e3e", - "b01581296b94dfcd" + "455a5266017ea121", + "50f73cee213ec05c" ], [ - "9c51aa678f16980f" + "264eece408043021" ] ] }, { - "id": "76acd48a511a5e3e", + "id": "455a5266017ea121", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -5586,19 +4295,19 @@ "topic": "", "name": "", "x": 890, - "y": 1280, + "y": 1300, "wires": [ [] ] }, { - "id": "5f50ed3f6ba37cef", + "id": "c368df68593bc2bf", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "label": "Token", "tooltip": "", - "group": "3b4bd36726be16d5", + "group": "12b719cba49817c9", "order": 2, "width": 6, "height": 1, @@ -5610,68 +4319,69 @@ "className": "", "topicType": "str", "x": 350, - "y": 1340, + "y": 1360, "wires": [ [ - "cb62d30728af2968" + "18fd1afa768187b3" ] ] }, { - "id": "cb62d30728af2968", + "id": "18fd1afa768187b3", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Save?", "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", "outputs": 2, "x": 470, - "y": 1340, + "y": 1360, "wires": [ [ - "94e503dd2e64d903" + "418aea2ec65573a0" ], [ - "d859bb39914d4999" + "9792c89c5f4429f9" ] ] }, { - "id": "0dd01eef6e70059e", + "id": "f90a98899b7a71d0", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "text", "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", "outputs": 1, "x": 230, - "y": 1340, + "y": 1360, "wires": [ [ - "5f50ed3f6ba37cef" + "c368df68593bc2bf" ] ] }, { - "id": "788fabff98c7973c", + "id": "b4c843620c251c43", "type": "link in", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "token", "links": [ "960912e90ba5b5bc", - "b01581296b94dfcd", - "d859bb39914d4999" + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" ], "x": 75, - "y": 1340, + "y": 1360, "wires": [ [ - "0dd01eef6e70059e" + "f90a98899b7a71d0" ] ] }, { - "id": "94e503dd2e64d903", + "id": "418aea2ec65573a0", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -5684,74 +4394,75 @@ "topic": "", "name": "", "x": 610, - "y": 1320, + "y": 1340, "wires": [ [ - "88e92b621d2a3394" + "f40286c18afd4501" ] ] }, { - "id": "d859bb39914d4999", + "id": "9792c89c5f4429f9", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "mode": "link", "links": [ - "788fabff98c7973c" + "b4c843620c251c43" ], "x": 555, - "y": 1360, + "y": 1380, "wires": [] }, { - "id": "9c51aa678f16980f", + "id": "264eece408043021", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "links": [ "5d267acc10020091", - "397ab7f44b893c89" + "3876d5cbd248592b" ], "x": 835, - "y": 1360, + "y": 1380, "wires": [] }, { - "id": "397ab7f44b893c89", + "id": "3876d5cbd248592b", "type": "link in", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "OSCparameters", "links": [ "960912e90ba5b5bc", - "9c51aa678f16980f", - "b42e061fb1f1f3d7" + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" ], "x": 75, - "y": 1380, + "y": 1400, "wires": [ [ - "a7fd00943edc380b" + "5daca3ec47f8e7fc" ] ] }, { - "id": "b01581296b94dfcd", + "id": "50f73cee213ec05c", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "links": [ - "788fabff98c7973c", + "b4c843620c251c43", "5d267acc10020091" ], "x": 835, - "y": 1320, + "y": 1340, "wires": [] }, { - "id": "bf6d941ad307ce22", + "id": "95578e54a9b61cba", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "prompt", "displayTime": "3", "highlight": "", @@ -5764,35 +4475,35 @@ "topic": "", "name": "", "x": 250, - "y": 1520, + "y": 1540, "wires": [ [ - "f22dfef37d5de773" + "d7a5693da7855da8" ] ] }, { - "id": "f22dfef37d5de773", + "id": "d7a5693da7855da8", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", "outputs": 2, "x": 390, - "y": 1520, + "y": 1540, "wires": [ [ - "54602ee49ca022e7" + "2b02b97dd1614e52" ], [ - "1505f3e72f971081" + "183a629accb417b1" ] ] }, { - "id": "1505f3e72f971081", + "id": "183a629accb417b1", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -5805,15 +4516,15 @@ "topic": "", "name": "", "x": 530, - "y": 1560, + "y": 1580, "wires": [ [] ] }, { - "id": "54602ee49ca022e7", + "id": "2b02b97dd1614e52", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "prompt", "displayTime": "3", "highlight": "", @@ -5826,17 +4537,17 @@ "topic": "", "name": "", "x": 530, - "y": 1520, + "y": 1540, "wires": [ [ - "f9efcb87b74abbd4" + "3e4c15d7b538f816" ] ] }, { - "id": "510dbe4d76253bd6", + "id": "3bf622f344172721", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "prompt", "displayTime": "3", "highlight": "", @@ -5849,35 +4560,35 @@ "topic": "", "name": "", "x": 810, - "y": 1520, + "y": 1540, "wires": [ [ - "600b2306caed1640" + "e431cb2b8d217cee" ] ] }, { - "id": "600b2306caed1640", + "id": "e431cb2b8d217cee", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", "outputs": 1, "x": 950, - "y": 1520, + "y": 1540, "wires": [ [ - "bbad1ab5f8f63fb7" + "106874534890f229" ] ] }, { - "id": "d34cd203725bac15", + "id": "a38d7fde5c73210f", "type": "ui_button", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Register", - "group": "3b4bd36726be16d5", - "order": 7, + "group": "12b719cba49817c9", + "order": 6, "width": 2, "height": 1, "passthru": false, @@ -5892,17 +4603,17 @@ "topic": "Requesting an OpenScanCloud Token", "topicType": "str", "x": 100, - "y": 1520, + "y": 1540, "wires": [ [ - "bf6d941ad307ce22" + "95578e54a9b61cba" ] ] }, { - "id": "bbad1ab5f8f63fb7", + "id": "106874534890f229", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -5915,67 +4626,67 @@ "topic": "", "name": "", "x": 1090, - "y": 1520, + "y": 1540, "wires": [ [] ] }, { - "id": "a7fd00943edc380b", + "id": "5daca3ec47f8e7fc", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", "outputs": 1, "x": 230, - "y": 1380, + "y": 1400, "wires": [ [ - "f7bf47e3eec6d736", - "b52d91c628b151a4", - "1969c709ef2fd1d5" + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" ] ] }, { - "id": "124459147143ec6a", + "id": "f34de19d4cf810a9", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Motor", "info": "", "x": 90, - "y": 1600, + "y": 1740, "wires": [] }, { - "id": "dbd62b91a6c9c412", + "id": "26c2b58e21f97475", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Camera", "info": "", "x": 90, - "y": 2240, + "y": 2500, "wires": [] }, { - "id": "842b6fe016087ce3", + "id": "a8ec972bad47a9a8", "type": "comment", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Pinout", "info": "", - "x": 110, - "y": 2860, + "x": 90, + "y": 2960, "wires": [] }, { - "id": "8c1a92f2dcc976c7", + "id": "b03e8b51187e88eb", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "Rotor_delay (ms)", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 14, + "group": "7a3279eea439bcdd", + "order": 16, "width": 3, "height": 1, "passthru": false, @@ -5987,22 +4698,22 @@ "step": "0.005", "className": "", "x": 450, - "y": 1840, + "y": 2100, "wires": [ [ - "bb54bbdae6690576" + "11fd3363416433f9" ] ] }, { - "id": "2647111c06f2055d", + "id": "6aae9d4fddf08cc0", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt delay", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 27, + "group": "7a3279eea439bcdd", + "order": 30, "width": 3, "height": 1, "passthru": false, @@ -6014,22 +4725,22 @@ "step": "0.005", "className": "", "x": 420, - "y": 2080, + "y": 2340, "wires": [ [ - "fb8145a9f8d4f7b2" + "e50492d1e18f43c6" ] ] }, { - "id": "f9b51424edb0491c", + "id": "543e1690693acbeb", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_acc", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 16, + "group": "7a3279eea439bcdd", + "order": 18, "width": 3, "height": 1, "passthru": false, @@ -6041,22 +4752,22 @@ "step": "0.1", "className": "", "x": 420, - "y": 1880, + "y": 2140, "wires": [ [ - "ea87ecfd2af3cc7f" + "e8b24efb0f30288e" ] ] }, { - "id": "1ab34b0a78b2c577", + "id": "9a56c087d941f1da", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_accramp", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 18, + "group": "7a3279eea439bcdd", + "order": 20, "width": 3, "height": 1, "passthru": false, @@ -6068,22 +4779,22 @@ "step": "100", "className": "", "x": 440, - "y": 1920, + "y": 2180, "wires": [ [ - "249f44c3a87793ba" + "29f576be9e292232" ] ] }, { - "id": "1d4230b3d9b93f63", + "id": "dfdebe10dbf0e198", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_stepsperrotation", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 12, + "group": "7a3279eea439bcdd", + "order": 14, "width": 3, "height": 1, "passthru": false, @@ -6094,19 +4805,19 @@ "className": "", "topicType": "msg", "x": 460, - "y": 1800, + "y": 2060, "wires": [ [ - "0bb56b1edb12c2cf" + "78e256083f59f66f" ] ] }, { - "id": "2e3222f0aba88040", + "id": "af8dfe78cbd0c301", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 17, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, "width": 3, "height": 1, "name": "rotor Accramp", @@ -6115,15 +4826,15 @@ "layout": "row-left", "className": "", "x": 780, - "y": 1880, + "y": 2140, "wires": [] }, { - "id": "9d50311679acf215", + "id": "ee4b8908a5b83880", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 11, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, "width": 3, "height": 1, "name": "rotor_Steps per Rotation", @@ -6132,15 +4843,15 @@ "layout": "row-spread", "className": "", "x": 810, - "y": 1920, + "y": 2180, "wires": [] }, { - "id": "25d7b4dd2aab8f05", + "id": "c4deaa38c1b0adbf", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 15, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, "width": 3, "height": 1, "name": "rotor Acc", @@ -6149,15 +4860,15 @@ "layout": "row-left", "className": "", "x": 760, - "y": 1840, + "y": 2100, "wires": [] }, { - "id": "15682cca9622831f", + "id": "baec873a95fff48a", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 13, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, "width": 3, "height": 1, "name": "rotor_delay", @@ -6166,15 +4877,15 @@ "layout": "row-left", "className": "", "x": 770, - "y": 1800, + "y": 2060, "wires": [] }, { - "id": "8e2d22042bfcb4e8", + "id": "355e89ab4e5484e4", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 23, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, "width": 6, "height": 1, "name": "tt", @@ -6183,18 +4894,18 @@ "layout": "row-center", "className": "", "x": 90, - "y": 2040, + "y": 2300, "wires": [] }, { - "id": "56bc3b93af2ebe16", + "id": "10687d331a732790", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_acc", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 29, + "group": "7a3279eea439bcdd", + "order": 32, "width": 3, "height": 1, "passthru": false, @@ -6206,22 +4917,22 @@ "step": "0.1", "className": "", "x": 410, - "y": 2120, + "y": 2380, "wires": [ [ - "35422077b53da9bf" + "af88b9da72917d62" ] ] }, { - "id": "6ef996f8a36f94c2", + "id": "721b9680a3fa460e", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_accramp", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 31, + "group": "7a3279eea439bcdd", + "order": 34, "width": 3, "height": 1, "passthru": false, @@ -6233,22 +4944,22 @@ "step": "1", "className": "", "x": 430, - "y": 2160, + "y": 2420, "wires": [ [ - "2c000bd53cdb98ca" + "b1b4678827d3a6dd" ] ] }, { - "id": "0c50fdbb5ac3c373", + "id": "c6642c7470d3820c", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_stepsperrotation", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 25, + "group": "7a3279eea439bcdd", + "order": 28, "width": 3, "height": 1, "passthru": false, @@ -6259,19 +4970,19 @@ "className": "", "topicType": "msg", "x": 450, - "y": 2040, + "y": 2300, "wires": [ [ - "485a4bed5a6bea23" + "eef89545ec0f6aa8" ] ] }, { - "id": "213ccfb441a42890", + "id": "18e5918748660109", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 30, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, "width": 3, "height": 1, "name": "ttAccramp", @@ -6280,15 +4991,15 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2160, + "y": 2420, "wires": [] }, { - "id": "73c9b4d09dc25e54", + "id": "8e805244dc1899e8", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 24, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, "width": 3, "height": 1, "name": "tt_steps per Rotation", @@ -6297,15 +5008,15 @@ "layout": "row-spread", "className": "", "x": 800, - "y": 2040, + "y": 2300, "wires": [] }, { - "id": "a81824c92f22487d", + "id": "a09e5fbea861bfb1", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 28, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, "width": 3, "height": 1, "name": "tt Acc", @@ -6314,15 +5025,15 @@ "layout": "row-left", "className": "", "x": 750, - "y": 2120, + "y": 2380, "wires": [] }, { - "id": "9715161858f69649", + "id": "7b06448b3b222011", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 26, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, "width": 3, "height": 1, "name": "tt_delay", @@ -6331,18 +5042,18 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2080, + "y": 2340, "wires": [] }, { - "id": "1b3ac50d2c6600c6", + "id": "0dfc86d90258f9bb", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_angle", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 20, + "group": "7a3279eea439bcdd", + "order": 22, "width": 3, "height": 1, "passthru": false, @@ -6354,19 +5065,19 @@ "step": "1", "className": "", "x": 430, - "y": 1960, + "y": 2220, "wires": [ [ - "e0d7c36daa42b3f3" + "c4b5a38c5c1df3d2" ] ] }, { - "id": "6dcd1f0ccb01a299", + "id": "9319d7d4f34c6d22", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 19, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, "width": 3, "height": 1, "name": "rotor_angle", @@ -6375,18 +5086,18 @@ "layout": "row-spread", "className": "", "x": 770, - "y": 1960, + "y": 2220, "wires": [] }, { - "id": "16e9a3a71c4bb916", + "id": "1610895f430b9aca", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_angle", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 33, + "group": "7a3279eea439bcdd", + "order": 36, "width": 3, "height": 1, "passthru": false, @@ -6398,19 +5109,19 @@ "step": "1", "className": "", "x": 420, - "y": 2200, + "y": 2460, "wires": [ [ - "c34111aaec734dd9" + "0f3367983bb8e159" ] ] }, { - "id": "888161059eb9c71c", + "id": "96a9febc0928b6f0", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 32, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, "width": 3, "height": 1, "name": "tt_angle", @@ -6419,15 +5130,15 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2200, + "y": 2460, "wires": [] }, { - "id": "f4fc72297074c7ae", + "id": "e2c5ea8c16a5ea32", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 4, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, "width": 6, "height": 1, "name": "rotor", @@ -6436,18 +5147,18 @@ "layout": "row-center", "className": "", "x": 90, - "y": 1680, + "y": 1820, "wires": [] }, { - "id": "9b1d8f9e21b34102", + "id": "277037c4716d85bf", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "tt_dir", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 35, + "group": "7a3279eea439bcdd", + "order": 38, "width": 3, "height": 1, "passthru": false, @@ -6459,22 +5170,22 @@ "step": "1", "className": "", "x": 410, - "y": 2240, + "y": 2500, "wires": [ [ - "89dbbe7d99ddbbaf" + "c9d2e31514def4fc" ] ] }, { - "id": "b2e839fe47a32b5f", + "id": "1361134e9847f003", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotor_dir", "label": "", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 22, + "group": "7a3279eea439bcdd", + "order": 24, "width": 3, "height": 1, "passthru": false, @@ -6486,19 +5197,19 @@ "step": "1", "className": "", "x": 420, - "y": 2000, + "y": 2260, "wires": [ [ - "204b0a5c8629d78a" + "523717b0f218a5fd" ] ] }, { - "id": "4519daf0b4b28aef", + "id": "6b0d58943ecb8bb2", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 34, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, "width": 3, "height": 1, "name": "tt_dir", @@ -6507,15 +5218,15 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2240, + "y": 2500, "wires": [] }, { - "id": "5f269ea2c8a53f6c", + "id": "08f93dd2aeedb391", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 21, + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, "width": 3, "height": 1, "name": "rotor_dir", @@ -6524,74 +5235,47 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2000, + "y": 2260, "wires": [] }, { - "id": "b67dfacfc9a23aa5", + "id": "46b91bef44714366", "type": "link in", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "advanced settings", "links": [ - "62cd775a1c02dac8" + "8750ad979e9ea246" ], "x": 95, - "y": 80, + "y": 100, "wires": [ [ - "9b756a1f9b0e7317" + "89eedf29b404f750" ] ] }, { - "id": "62cd775a1c02dac8", + "id": "8750ad979e9ea246", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "", "mode": "link", "links": [ - "b67dfacfc9a23aa5" + "46b91bef44714366" ], "x": 955, - "y": 660, + "y": 480, "wires": [] }, { - "id": "9d94dbc523d989a3", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_delay_after", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 16, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 450, - "y": 2460, - "wires": [ - [ - "b81e238ccd0a04fe" - ] - ] - }, - { - "id": "0558d6eb9a01862e", + "id": "2522f888dc58972f", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_delay_before", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 14, + "group": "d324f0b852c2df0a", + "order": 7, "width": 3, "height": 1, "passthru": false, @@ -6599,53 +5283,26 @@ "topic": "", "topicType": "str", "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 440, - "y": 2500, - "wires": [ - [ - "a0048747e7300bdc" - ] - ] - }, - { - "id": "d47515c9b208bfb7", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_timeout", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 12, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.01", "max": "1", - "step": "0.01", + "step": "0.02", "className": "", - "x": 420, - "y": 2420, + "x": 430, + "y": 2600, "wires": [ [ - "9b0d5c521a7822cc" + "5c752757090c49d2" ] ] }, { - "id": "89c76766c7552b57", + "id": "30e8df3d616512d8", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_gain", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 22, + "group": "d324f0b852c2df0a", + "order": 11, "width": 3, "height": 1, "passthru": false, @@ -6656,77 +5313,23 @@ "max": "10", "step": "0.1", "className": "", - "x": 410, - "y": 2540, - "wires": [ - [ - "9b26ed02296d27c9" - ] - ] - }, - { - "id": "c385518eb65a1b27", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_awbg_red", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 18, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-10", - "max": "10", - "step": "0.1", - "className": "", - "x": 430, - "y": 2580, - "wires": [ - [ - "b0ac7e9a7c713b84" - ] - ] - }, - { - "id": "5c80833b718d9bf6", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "cam_awbg_blue", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 20, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-10", - "max": "10", - "step": "0.1", - "className": "", - "x": 430, - "y": 2620, + "x": 400, + "y": 2640, "wires": [ [ - "827b1a671a77037d" + "a1769f0277834f6d" ] ] }, { - "id": "5a3826e112fb24e6", + "id": "d855d926df89d65b", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_contrast", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 24, + "group": "d324f0b852c2df0a", + "order": 13, "width": 3, "height": 1, "passthru": false, @@ -6737,23 +5340,24 @@ "max": "5", "step": "0.1", "className": "", - "x": 430, - "y": 2660, + "x": 420, + "y": 2760, "wires": [ [ - "78a1536c167da741" + "1a8b0ba21b4f3005", + "654bc70a18820828" ] ] }, { - "id": "3182ed7ac02b1509", + "id": "7617517dc8ba2859", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_saturation", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 26, + "group": "d324f0b852c2df0a", + "order": 15, "width": 3, "height": 1, "passthru": false, @@ -6764,22 +5368,23 @@ "max": "5", "step": "0.1", "className": "", - "x": 430, - "y": 2700, + "x": 420, + "y": 2800, "wires": [ [ - "fe9a5b68fc8c2077" + "dc8fc962ff7d594b", + "e64feb03a791ca33" ] ] }, { - "id": "7fa6337cdf0a0bc8", + "id": "cbaa23c34e10fae1", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_jpeg_q", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", + "group": "d324f0b852c2df0a", "order": 3, "width": 3, "height": 1, @@ -6791,54 +5396,20 @@ "max": "100", "step": "1", "className": "", - "x": 420, - "y": 2740, + "x": 410, + "y": 2840, "wires": [ [ - "e27d2613e942f344" + "00e7836ccb3c4d0c" ] ] }, { - "id": "08275bf96f87b8ef", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 11, - "width": 3, - "height": 1, - "name": "timeout", - "label": "Timeout", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2420, - "wires": [] - }, - { - "id": "d2d028df4a139f41", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 15, - "width": 3, - "height": 1, - "name": "delay_after", - "label": "Delay after", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2460, - "wires": [] - }, - { - "id": "c6a65762aa4ffb7b", + "id": "bbe443b039a14e21", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 13, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, "width": 3, "height": 1, "name": "delay_before", @@ -6846,16 +5417,16 @@ "format": "", "layout": "row-spread", "className": "", - "x": 770, - "y": 2500, + "x": 760, + "y": 2600, "wires": [] }, { - "id": "780323fd4504b855", + "id": "d320ed3d701e6cc2", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 21, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, "width": 3, "height": 1, "name": "gain", @@ -6863,50 +5434,16 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2540, - "wires": [] - }, - { - "id": "780bf08b41202135", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 17, - "width": 3, - "height": 1, - "name": "awbg red", - "label": "AWBG red", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2580, - "wires": [] - }, - { - "id": "c0faf441fc918538", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 19, - "width": 3, - "height": 1, - "name": "awbg blue", - "label": "AWBG blue", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2620, + "x": 740, + "y": 2640, "wires": [] }, { - "id": "93d12b447a39c5bb", + "id": "f5834dd4646c8af9", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 23, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, "width": 3, "height": 1, "name": "contrast", @@ -6914,16 +5451,16 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 2660, + "x": 750, + "y": 2760, "wires": [] }, { - "id": "e77e6dcd285d3062", + "id": "ae9a4e19469813ef", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 25, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, "width": 3, "height": 1, "name": "saturation", @@ -6931,15 +5468,15 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 2700, + "x": 750, + "y": 2800, "wires": [] }, { - "id": "a7075bc8d5ee1138", + "id": "bd629d0d31233c8b", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", "order": 2, "width": 3, "height": 1, @@ -6948,18 +5485,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2740, + "x": 740, + "y": 2840, "wires": [] }, { - "id": "282681e7c4351f74", + "id": "e89f61dbe6a6cffe", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ext", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 3, "width": 2, "height": 1, @@ -6970,19 +5507,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 2900, + "x": 390, + "y": 3000, "wires": [ [ - "b17e82651407d8e0" + "885bc559fafec5f2" ] ] }, { - "id": "da43c58979737fec", + "id": "ece38cb172a12d75", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 2, "width": 4, "height": 1, @@ -6991,18 +5528,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2900, + "x": 730, + "y": 3000, "wires": [] }, { - "id": "ef70d61678fe1f11", + "id": "70014da0b6ab6698", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "light1", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 5, "width": 2, "height": 1, @@ -7013,19 +5550,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 2940, + "x": 390, + "y": 3040, "wires": [ [ - "2c812acffdb330c5" + "f70321c96bf81360" ] ] }, { - "id": "fec56a7e913b21d6", + "id": "29634ea5f6d666df", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 4, "width": 4, "height": 1, @@ -7034,18 +5571,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2940, + "x": 730, + "y": 3040, "wires": [] }, { - "id": "24929b4629f22070", + "id": "2544963852c6881a", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "light2", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 7, "width": 2, "height": 1, @@ -7056,19 +5593,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 2980, + "x": 390, + "y": 3080, "wires": [ [ - "ae0654af69446942" + "95e1603bbd06a69d" ] ] }, { - "id": "7c6bdc0504aa4cc7", + "id": "27903533cd85a59e", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 6, "width": 4, "height": 1, @@ -7077,18 +5614,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 2980, + "x": 730, + "y": 3080, "wires": [] }, { - "id": "8c396b060f3d2646", + "id": "a1394401246eb735", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotordir", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 9, "width": 2, "height": 1, @@ -7099,19 +5636,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3020, + "x": 400, + "y": 3120, "wires": [ [ - "58cf48cfacc979fb" + "a8f92ea6bf394640" ] ] }, { - "id": "97568610daccf74a", + "id": "bc0aa4bacdfa94ea", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 8, "width": 4, "height": 1, @@ -7120,18 +5657,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3020, + "x": 740, + "y": 3120, "wires": [] }, { - "id": "a3c58ea48c388215", + "id": "f15ca4518b5f223e", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotorstep", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 11, "width": 2, "height": 1, @@ -7142,19 +5679,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3060, + "x": 400, + "y": 3160, "wires": [ [ - "c7ae206f2fff6810" + "06397bb46b3bb541" ] ] }, { - "id": "6da92aeaeffd95e0", + "id": "0d2924b160e7e383", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 10, "width": 4, "height": 1, @@ -7163,18 +5700,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3060, + "x": 740, + "y": 3160, "wires": [] }, { - "id": "9b5da90eaf6ac562", + "id": "49900bb9047dd965", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "rotoren", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 13, "width": 2, "height": 1, @@ -7185,19 +5722,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3100, + "x": 400, + "y": 3200, "wires": [ [ - "cfebd4a47a68b319" + "687dcdc1ede11700" ] ] }, { - "id": "12623e4addfa2c22", + "id": "a4d743ca73ee1622", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 12, "width": 4, "height": 1, @@ -7206,18 +5743,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3100, + "x": 740, + "y": 3200, "wires": [] }, { - "id": "f24cb404d7d09f8a", + "id": "5a90224dc998b417", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ttdir", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 15, "width": 2, "height": 1, @@ -7228,19 +5765,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 3140, + "x": 390, + "y": 3240, "wires": [ [ - "90f4d220928e4727" + "e220740c0d38ccb0" ] ] }, { - "id": "542bfb9d92935c2c", + "id": "67dc1b544c4ddf9f", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 14, "width": 4, "height": 1, @@ -7249,18 +5786,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 3140, + "x": 730, + "y": 3240, "wires": [] }, { - "id": "1f79467df98ce894", + "id": "d2364ab09627fe94", "type": "ui_text_input", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "ttstep", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 17, "width": 2, "height": 1, @@ -7271,19 +5808,19 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 3180, + "x": 390, + "y": 3280, "wires": [ [ - "b05e1e612887f9c2" + "79d7e5a705ab813a" ] ] }, { - "id": "170d3b925f7745cc", + "id": "145b67ac40721ba6", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 16, "width": 4, "height": 1, @@ -7292,18 +5829,18 @@ "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 3180, + "x": 730, + "y": 3280, "wires": [] }, { - "id": "65b0130e390c2e67", + "id": "eef25405472acfee", "type": "ui_text_input", - "z": "017bd4e4a428bee5", - "name": "tten", + "z": "e43a27722b508115", + "name": "endstop1", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 19, "width": 2, "height": 1, @@ -7314,39 +5851,39 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 410, - "y": 3220, + "x": 400, + "y": 3320, "wires": [ [ - "fe22723ce5a3495f" + "12d20f2274bcc511" ] ] }, { - "id": "10ac340984418a58", + "id": "35eb252a41413531", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 18, "width": 4, "height": 1, - "name": "tten", - "label": "Turntable enable", + "name": "endstop1", + "label": "Endstop Rotor", "format": "", "layout": "row-spread", "className": "", - "x": 750, - "y": 3220, + "x": 740, + "y": 3320, "wires": [] }, { - "id": "661614f5bd2c71d6", + "id": "74e455136b5ca5dd", "type": "ui_text_input", - "z": "017bd4e4a428bee5", - "name": "endstop1", + "z": "e43a27722b508115", + "name": "endstop2", "label": "", "tooltip": "", - "group": "644b3bcc903d46ca", + "group": "70d0be671bf03ca7", "order": 21, "width": 2, "height": 1, @@ -7357,78 +5894,35 @@ "sendOnBlur": true, "className": "", "topicType": "msg", - "x": 420, - "y": 3260, + "x": 400, + "y": 3360, "wires": [ [ - "2af447a6905b83bc" + "a4a89668ce4c9f05" ] ] }, { - "id": "c18b55859dae5f85", + "id": "3a74f653800eb831", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", "order": 20, "width": 4, "height": 1, - "name": "endstop1", - "label": "Endstop 1", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 3260, - "wires": [] - }, - { - "id": "e23a396162026618", - "type": "ui_text_input", - "z": "017bd4e4a428bee5", "name": "endstop2", - "label": "", - "tooltip": "", - "group": "644b3bcc903d46ca", - "order": 23, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 420, - "y": 3300, - "wires": [ - [ - "787a128f84f747c0" - ] - ] - }, - { - "id": "82c1a33014d003e9", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "644b3bcc903d46ca", - "order": 22, - "width": 4, - "height": 1, - "name": "endstop1", - "label": "Endstop 2", + "label": "Endstop Turntable", "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 3300, + "x": 740, + "y": 3360, "wires": [] }, { - "id": "5255759a7c5b2a74", + "id": "5fcef1cb2e9e4788", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", @@ -7441,37 +5935,37 @@ "topic": "", "name": "confirm", "x": 680, - "y": 660, + "y": 480, "wires": [ [ - "15fd1c9e5610cb85" + "29745a36fc157f3f" ] ] }, { - "id": "8be8015931c663cc", + "id": "f06a7bcad524e9f9", "type": "python3-function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", "outputs": 1, "x": 530, - "y": 660, + "y": 480, "wires": [ [ - "5255759a7c5b2a74" + "5fcef1cb2e9e4788" ] ] }, { - "id": "9d464b2ba1edaf48", + "id": "f455fb39039617ae", "type": "ui_slider", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "cam_rotation", "label": "", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 10, + "group": "d324f0b852c2df0a", + "order": 5, "width": 3, "height": 1, "passthru": false, @@ -7482,20 +5976,20 @@ "max": "270", "step": "90", "className": "", - "x": 420, - "y": 2780, + "x": 410, + "y": 2880, "wires": [ [ - "b7d3fe0c0b40b3e1" + "3019576de193d9d6" ] ] }, { - "id": "db98b95693ebce63", + "id": "fdfbc900fe424eb9", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 9, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, "width": 3, "height": 1, "name": "cam_rot", @@ -7503,14 +5997,14 @@ "format": "", "layout": "row-spread", "className": "", - "x": 760, - "y": 2780, + "x": 750, + "y": 2880, "wires": [] }, { - "id": "6659121906897a1f", + "id": "c3699d6b9664ccca", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7519,17 +6013,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1800, + "y": 2060, "wires": [ [ - "1d4230b3d9b93f63" + "dfdebe10dbf0e198" ] ] }, { - "id": "0bb56b1edb12c2cf", + "id": "78e256083f59f66f", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7538,15 +6032,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1800, + "y": 2060, "wires": [ [] ] }, { - "id": "569829eeff715c33", + "id": "0f9141b401322374", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7555,17 +6049,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1920, + "y": 2180, "wires": [ [ - "1ab34b0a78b2c577" + "9a56c087d941f1da" ] ] }, { - "id": "249f44c3a87793ba", + "id": "29f576be9e292232", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7574,15 +6068,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1920, + "y": 2180, "wires": [ [] ] }, { - "id": "c997e60519341afd", + "id": "23e3099b34c4e475", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7591,17 +6085,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1960, + "y": 2220, "wires": [ [ - "1b3ac50d2c6600c6" + "0dfc86d90258f9bb" ] ] }, { - "id": "e0d7c36daa42b3f3", + "id": "c4b5a38c5c1df3d2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7610,15 +6104,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1960, + "y": 2220, "wires": [ [] ] }, { - "id": "59ecf3a22cd3a669", + "id": "79a14162ac805fac", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7627,17 +6121,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2000, + "y": 2260, "wires": [ [ - "b2e839fe47a32b5f" + "1361134e9847f003" ] ] }, { - "id": "204b0a5c8629d78a", + "id": "523717b0f218a5fd", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7646,15 +6140,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2000, + "y": 2260, "wires": [ [] ] }, { - "id": "15f02421b30a9ab6", + "id": "f5cf780f3fa8997e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", "outputs": 1, @@ -7663,17 +6157,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1840, + "y": 2100, "wires": [ [ - "8c1a92f2dcc976c7" + "b03e8b51187e88eb" ] ] }, { - "id": "bb54bbdae6690576", + "id": "11fd3363416433f9", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7682,15 +6176,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 1840, + "y": 2100, "wires": [ [] ] }, { - "id": "58928befcc61b1f7", + "id": "02060b3f3b294563", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -7699,17 +6193,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 1880, + "y": 2140, "wires": [ [ - "f9b51424edb0491c" + "543e1690693acbeb" ] ] }, { - "id": "ea87ecfd2af3cc7f", + "id": "e8b24efb0f30288e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7718,34 +6212,34 @@ "finalize": "", "libs": [], "x": 630, - "y": 1880, + "y": 2140, "wires": [ [] ] }, { - "id": "27bc56f273360ac7", + "id": "de1ad8b27b72a5ac", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", "outputs": 1, - "noerr": 0, + "noerr": 4, "initialize": "", "finalize": "", "libs": [], "x": 290, - "y": 2040, + "y": 2300, "wires": [ [ - "0c50fdbb5ac3c373" + "c6642c7470d3820c" ] ] }, { - "id": "f46ced86106306c8", + "id": "ed4d587cb4feb064", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7754,17 +6248,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2160, + "y": 2420, "wires": [ [ - "6ef996f8a36f94c2" + "721b9680a3fa460e" ] ] }, { - "id": "4339704cd8552eb3", + "id": "5b02160c33605ae7", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7773,17 +6267,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2200, + "y": 2460, "wires": [ [ - "16e9a3a71c4bb916" + "1610895f430b9aca" ] ] }, { - "id": "1ac53bb6150645fe", + "id": "304c135ec09801e3", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, @@ -7792,17 +6286,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2240, + "y": 2500, "wires": [ [ - "9b1d8f9e21b34102" + "277037c4716d85bf" ] ] }, { - "id": "9b89eb1eaf333c10", + "id": "a91dcbe0f9a2416a", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", "outputs": 1, @@ -7811,17 +6305,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2080, + "y": 2340, "wires": [ [ - "2647111c06f2055d" + "6aae9d4fddf08cc0" ] ] }, { - "id": "2e8927be0e235fa1", + "id": "6b2eb1cb95e573f9", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, @@ -7830,17 +6324,17 @@ "finalize": "", "libs": [], "x": 290, - "y": 2120, + "y": 2380, "wires": [ [ - "56bc3b93af2ebe16" + "10687d331a732790" ] ] }, { - "id": "485a4bed5a6bea23", + "id": "eef89545ec0f6aa8", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7849,15 +6343,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2040, + "y": 2300, "wires": [ [] ] }, { - "id": "2c000bd53cdb98ca", + "id": "b1b4678827d3a6dd", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7866,15 +6360,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2160, + "y": 2420, "wires": [ [] ] }, { - "id": "c34111aaec734dd9", + "id": "0f3367983bb8e159", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7883,15 +6377,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2200, + "y": 2460, "wires": [ [] ] }, { - "id": "89dbbe7d99ddbbaf", + "id": "c9d2e31514def4fc", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7900,15 +6394,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2240, + "y": 2500, "wires": [ [] ] }, { - "id": "fb8145a9f8d4f7b2", + "id": "e50492d1e18f43c6", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7917,15 +6411,15 @@ "finalize": "", "libs": [], "x": 630, - "y": 2080, + "y": 2340, "wires": [ [] ] }, { - "id": "35422077b53da9bf", + "id": "af88b9da72917d62", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -7934,1119 +6428,1171 @@ "finalize": "", "libs": [], "x": 630, - "y": 2120, + "y": 2380, "wires": [ [] ] }, { - "id": "d5308090f2b7971a", + "id": "43fe948b3e7234e2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", - "func": "var file = 'cam_timeout'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2420, + "x": 280, + "y": 2600, "wires": [ [ - "d47515c9b208bfb7" + "2522f888dc58972f" ] ] }, { - "id": "9b0d5c521a7822cc", + "id": "5c752757090c49d2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_timeout'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2420, + "x": 620, + "y": 2600, "wires": [ [] ] }, { - "id": "694d1068bea15171", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2460, - "wires": [ - [ - "9d94dbc523d989a3" - ] - ] - }, - { - "id": "cec3e5e78a40476b", + "id": "435681b3f7625a7e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", - "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2500, + "x": 280, + "y": 2640, "wires": [ [ - "0558d6eb9a01862e" + "30e8df3d616512d8" ] ] }, { - "id": "b81e238ccd0a04fe", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2460, - "wires": [ - [] - ] - }, - { - "id": "a0048747e7300bdc", + "id": "a1769f0277834f6d", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2500, + "x": 620, + "y": 2640, "wires": [ [] ] }, { - "id": "6f524f9370a18482", + "id": "1de07c7d285cbaf3", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", - "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2540, + "x": 280, + "y": 2760, "wires": [ [ - "89c76766c7552b57" + "d855d926df89d65b" ] ] }, { - "id": "9b26ed02296d27c9", + "id": "1a8b0ba21b4f3005", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2540, + "x": 620, + "y": 2760, "wires": [ [] ] }, { - "id": "1f87f473e327c3cc", + "id": "ebc9e283468eda31", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadF", - "func": "var file = 'cam_awbg_red'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2580, + "x": 280, + "y": 2800, "wires": [ [ - "c385518eb65a1b27" + "7617517dc8ba2859" ] ] }, { - "id": "b0ac7e9a7c713b84", + "id": "dc8fc962ff7d594b", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_awbg_red'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2580, + "x": 620, + "y": 2800, "wires": [ [] ] }, { - "id": "cff7ac5f1e061855", + "id": "60d641613527c736", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_awbg_blue'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2620, + "x": 280, + "y": 2840, "wires": [ [ - "5c80833b718d9bf6" + "cbaa23c34e10fae1" ] ] }, { - "id": "827b1a671a77037d", + "id": "00e7836ccb3c4d0c", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_awbg_blue'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2620, + "x": 620, + "y": 2840, "wires": [ [] ] }, { - "id": "cf854461c37ca54f", + "id": "7f24c0c34a88ba04", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2660, + "x": 280, + "y": 2880, "wires": [ [ - "5a3826e112fb24e6" + "f455fb39039617ae" ] ] }, { - "id": "78a1536c167da741", + "id": "3019576de193d9d6", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2660, + "x": 620, + "y": 2880, "wires": [ [] ] }, { - "id": "ba10e04dd1761692", + "id": "77bb7dc529d63a7e", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadF", - "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2700, + "x": 270, + "y": 3000, "wires": [ [ - "3182ed7ac02b1509" + "e89f61dbe6a6cffe" ] ] }, { - "id": "fe9a5b68fc8c2077", + "id": "885bc559fafec5f2", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2700, + "x": 590, + "y": 3000, "wires": [ [] ] }, { - "id": "a69d216114f908a5", + "id": "cc6dabe017a9c8a8", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2740, + "x": 270, + "y": 3320, "wires": [ [ - "7fa6337cdf0a0bc8" + "eef25405472acfee" ] ] }, { - "id": "e27d2613e942f344", + "id": "12d20f2274bcc511", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2740, + "x": 590, + "y": 3320, "wires": [ [] ] }, { - "id": "f02d4a036a225e87", + "id": "dcb9fed8122759fd", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2780, + "x": 270, + "y": 3040, "wires": [ [ - "9d464b2ba1edaf48" + "70014da0b6ab6698" ] ] }, { - "id": "b7d3fe0c0b40b3e1", + "id": "f70321c96bf81360", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2780, + "x": 590, + "y": 3040, "wires": [ [] ] }, { - "id": "612cccacda1a65aa", + "id": "013d2057c2347a62", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2900, + "x": 270, + "y": 3080, "wires": [ [ - "282681e7c4351f74" + "2544963852c6881a" ] ] }, { - "id": "b17e82651407d8e0", + "id": "95e1603bbd06a69d", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 2900, + "x": 590, + "y": 3080, "wires": [ [] ] }, { - "id": "3b126549c03a872e", + "id": "f88bbf11d5aa9a14", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'pin_endstop1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3260, + "x": 270, + "y": 3120, "wires": [ [ - "661614f5bd2c71d6" + "a1394401246eb735" ] ] }, { - "id": "2af447a6905b83bc", + "id": "a8f92ea6bf394640", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_endstop1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3260, + "x": 590, + "y": 3120, "wires": [ [] ] }, { - "id": "954db931f87894ee", + "id": "301af70731e096e5", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2940, + "x": 270, + "y": 3160, "wires": [ [ - "ef70d61678fe1f11" + "f15ca4518b5f223e" ] ] }, { - "id": "2c812acffdb330c5", + "id": "06397bb46b3bb541", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 2940, + "x": 590, + "y": 3160, "wires": [ [] ] }, { - "id": "6682c8057e89d087", + "id": "0456a9ec4c236c9e", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2980, + "x": 270, + "y": 3200, "wires": [ [ - "24929b4629f22070" + "49900bb9047dd965" ] ] }, { - "id": "ae0654af69446942", + "id": "687dcdc1ede11700", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 2980, + "x": 590, + "y": 3200, "wires": [ [] ] }, { - "id": "015be401d08047d2", + "id": "09d37ba08ec0f163", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3020, + "x": 270, + "y": 3240, "wires": [ [ - "8c396b060f3d2646" + "5a90224dc998b417" ] ] }, { - "id": "58cf48cfacc979fb", + "id": "37d954a4cf7e87ea", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3020, + "x": 270, + "y": 3280, "wires": [ - [] + [ + "d2364ab09627fe94" + ] ] }, { - "id": "1c6c0f8b9ac95659", + "id": "e220740c0d38ccb0", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3060, + "x": 590, + "y": 3240, "wires": [ - [ - "a3c58ea48c388215" - ] + [] ] }, { - "id": "c7ae206f2fff6810", + "id": "79d7e5a705ab813a", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3060, + "x": 590, + "y": 3280, "wires": [ [] ] }, { - "id": "dcee66c0d56c6934", + "id": "21dc963d967d9c99", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3100, + "x": 270, + "y": 3360, "wires": [ [ - "9b5da90eaf6ac562" + "74e455136b5ca5dd" ] ] }, { - "id": "cfebd4a47a68b319", + "id": "a4a89668ce4c9f05", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3100, + "x": 590, + "y": 3360, "wires": [ [] ] }, { - "id": "6ec7d85bb17eb159", + "id": "22ef66b0e2058be2", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3140, + "x": 270, + "y": 360, "wires": [ [ - "f24cb404d7d09f8a" + "cb3437ec113e1b6f" ] ] }, { - "id": "4f42d02a3776a006", + "id": "9ce01c8ba97932c1", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3180, + "x": 270, + "y": 400, "wires": [ [ - "1f79467df98ce894" + "60fd0adce1cfeb82" ] ] }, { - "id": "5d70f4715c9a5ae1", + "id": "81356177176eebcf", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'pin_tt_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 3220, + "x": 270, + "y": 480, "wires": [ [ - "65b0130e390c2e67" + "f6d6cc35679ede63" ] ] }, { - "id": "90f4d220928e4727", + "id": "b78346ca3ce70c68", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3140, + "x": 270, + "y": 320, "wires": [ - [] + [ + "f0d8dbcca76a1926" + ] ] }, { - "id": "b05e1e612887f9c2", + "id": "e95b86cbac1b03b9", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3180, + "x": 550, + "y": 320, "wires": [ [] ] }, { - "id": "fe22723ce5a3495f", + "id": "3e4c15d7b538f816", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'pin_tt_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3220, + "x": 670, + "y": 1540, "wires": [ - [] + [ + "3bf622f344172721" + ] ] }, { - "id": "58bbe9fc41e0d7b9", + "id": "0f0871baf322b6d0", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'pin_endstop2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 290, - "y": 3300, + "y": 1820, "wires": [ [ - "e23a396162026618" + "6ebd15c61a5ca891" ] ] }, { - "id": "787a128f84f747c0", + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'pin_endstop2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 3300, + "x": 630, + "y": 1820, "wires": [ [] ] }, { - "id": "78351089ee9ebeaf", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 340, + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, "wires": [ [ - "40dee936a9abac0d" + "acd10a4c99ee8063" ] ] }, { - "id": "5fba78ae65eaaf5d", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 380, + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, "wires": [ [ - "4fd9bb53fdb51a25" + "031d7697768d0e77" ] ] }, { - "id": "67206663b3881868", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 660, + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, "wires": [ [ - "c833f6243a059d83" + "be1954dd71d2c94c" ] ] }, { - "id": "3492754252645e62", + "id": "edb1c8fae8b65c82", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'camera'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 420, + "x": 290, + "y": 1860, "wires": [ [ - "a2c1dba3e67be015", - "6f3d403e157163e4" + "3ad0f0f206e4a873" ] ] }, { - "id": "d16525a31223bc42", + "id": "031d7697768d0e77", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'model'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 620, + "x": 630, + "y": 1860, "wires": [ - [ - "80b579a4220e5c23", - "c6138801b30f091d" - ] + [] ] }, { - "id": "f99ec8781a33ec7d", + "id": "462a8f3ca75fc3c8", "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 300, + "x": 290, + "y": 1900, "wires": [ [ - "7dc39bd847d16ded" + "3b6d759ed5be647f" ] ] }, { - "id": "5f849178998d9082", + "id": "be1954dd71d2c94c", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "if(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 550, - "y": 300, + "x": 630, + "y": 1900, "wires": [ [] ] }, { - "id": "725fd0cab0bddc0e", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 940, + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, "wires": [ [ - "49259adad52fc214" + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" ] ] }, { - "id": "49259adad52fc214", - "type": "ui_text_input", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Hostname", - "tooltip": "", - "group": "0fe66c9190b8a87c", - "order": 6, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "Change hostname to:", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 530, + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, "y": 940, "wires": [ [ - "8001f7c361de7d8c" + "ded3086945a6d4b5", + "6ea3cdab41f20f92" ] ] }, { - "id": "51521bc6eb44cde5", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 980, + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, "wires": [ [ - "59c9f67283ba1709" + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" ] ] }, { - "id": "2bb52656f9554dab", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "ssid = msg.payload\nmsg.topic = 'Add wifi network (' + ssid + ')'\nmsg.payload = 'Enter Wifi password:'\nmsg.ssid = ssid\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 650, - "y": 980, + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, "wires": [ [ - "ebcc98685059b9d4" + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" ] ] }, { - "id": "ebce67b739d1891f", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "chk/change hostname", - "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n pass\n\nwith open('/etc/hostname', 'r') as file:\n old_hostname = file.read().replace('\\n','')\n\nhostname = msg['hostname']\nif len(hostname) < 4 :\n msg['payload'] = ' '\n msg['topic'] = 'ERROR - Hostname NOT changed'\n return msg\n \n\nwith open('/etc/hostname', 'w+') as file:\n file.write(hostname)\nos.system('echo ' + hostname + ' | tee /etc/hostname')\nwith open('/etc/hosts', 'r') as file:\n temp = file.read()\ntemp = temp.replace(old_hostname,hostname)\nwith open('/etc/hosts', 'w') as file:\n file.write(temp)\nos.system('hostnamectl set-hostname ' + hostname)\nos.system('systemctl restart avahi-daemon')\nsave('hostname',hostname)\nmsg['payload'] = hostname\nmsg['topic'] = 'Success - Hostname changed'\nreturn msg\n", - "outputs": 1, - "x": 1140, - "y": 940, + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, "wires": [ [ - "03732a7d3b0c95aa" + "53e6681d7254d484" ] ] }, { - "id": "667ac2aba819f506", + "id": "53e6681d7254d484", "type": "ui_toast", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "position": "dialog", "displayTime": "3", "highlight": "", "sendall": true, "outputs": 1, - "ok": "OK", - "cancel": "Cancel", + "ok": "No", + "cancel": "Yes", "raw": false, "className": "", "topic": "", - "name": "Confirm", - "x": 920, - "y": 940, - "wires": [ - [ - "ebce67b739d1891f" - ] - ] - }, - { - "id": "8001f7c361de7d8c", - "type": "change", - "z": "017bd4e4a428bee5", "name": "", - "rules": [ - { - "t": "set", - "p": "hostname", - "pt": "msg", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 710, - "y": 940, - "wires": [ - [ - "667ac2aba819f506" - ] - ] - }, - { - "id": "9bb0adbd716ce347", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "reboot", - "links": [ - "16c76929f88df841", - "fe3a855fee9e28c6" - ], - "x": 155, - "y": 720, + "x": 270, + "y": 620, "wires": [ [ - "d114f4d4d7f31981", - "cc3cb10f2ea3f8b8" + "c11e79cfa7bc10b7" ] ] }, { - "id": "f9efcb87b74abbd4", + "id": "c11e79cfa7bc10b7", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "msg", - "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 670, - "y": 1520, + "x": 410, + "y": 620, "wires": [ [ - "510dbe4d76253bd6" + "307782d10c1acdaf" ] ] }, { - "id": "adc206aa8edd1e41", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "OSC", - "group": "db43d646.2074c8", - "order": 2, - "width": 3, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, "className": "", - "icon": "fa-question-circle", - "payload": "

Files&Cloud

Refresh

You can refresh the status of the processing of your files in the OpenScanCloud. Make sure to read and agree the terms of use (in settings menu) before using the OpenScanCloud. Do not spam this button, as this might lead to temporary/permanent suspension of your IP address.

The status (in the table) of the individual sets in the file list will be updated to one of the following:

Created - you started the upload of your image set. If you are stuck on this status, please try to restart the upload.

Initialized - all files have been uploaded and processing will start as soon as possible

File approved - the server received and verified your files

Processing started - your files are currently being processed

Processing failed - there are various reasons why processing might fail. Please check the email for more details or contact me at cloud@openscan.eu

processing done - check your email, where you should find a link to the 3d model :)

Status (on the right column)

Indicates, what the device is currently up to.

Refreshing - updating all image set's status

Uploading - while transferring the image set to the OpenScanCloud servers. If the upload freezes, be patient. If nothing happens, reboot the device and restart the upload.

Project started - when the upload of a set was successful

Zipping - files larger then 200mb have to be split and re-zipped before uploading to the OpenScanCloud, the process might take a while depending on the filesize.

Combining - two sets into one might take up to a minute.

Set

select a set from the file list by clicking on a row in the table

Download

Download the selected set from the OpenScan device to your computer/mobile/tablet

Upload

Upload the selected file to the OpenScanCloud

Combine

In order to combine two sets, select one set. Click the combine button and select the second set. A pop-up will appear, and you can confirm the operation. All images from the two sets will be merged into one set. The original image sets will be deleted!

Delete Set/All

Please keep in mind, that the memory of the SD card is relatively small, and thus you will have to delete individual or all photo sets from time to time.

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 590, - "y": 200, + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, "wires": [ - [ - "f304680180a23479" - ] + [] ] }, { - "id": "45df91cae421e8e1", + "id": "cca3300a8f0daf4d", "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Scan_settings", - "group": "7aaf184330605300", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", "order": 1, "width": 6, "height": 1, @@ -9057,726 +7603,778 @@ "bgcolor": "transparent", "className": "", "icon": "fa-question-circle", - "payload": "

Scan Settings

Current Status

--READY-- - everything is okay and ready to go :)

Routine-preparing - before starting the routine some time might pass depending on the number of photos

Routine-stopping - manually ending the routine by pressing the stop button

Routine-Photo X/Y - Showing the progress of the routine

No Camera Found - please check the camera ribbon cable

Error: XXX - Please contact info@openscan.eu or post an issue on Github.com

Projectname

Each photo set will be saved using the following pattern  YYYY-MM-DD_hh-mm-ss_projectname.zip (e.g. 2022-04-05_12.12.12_toysoldier.zip). Keep your files organized by giving each set a new projectname. If not specified 'default' will be used.

Rotor

Moving the rotor by increments of 5°. Please make sure to start the routine with the camera in the horizontal position.

Turntable

Moving the turntable by increments of 15°.

Ringlight

Use the ring light for shadow-free illumination. It is highly recommended to use the polarizer in order to avoid reflections. Note, that the polarizer will absorb 75% of the light, so you might need to use both ring lights.

Photos

Set the number of photos for the current set. 60-120 photos should be more than enough for most objects. If the reconstruction fails or is very bad with 60 photos, increasing the number of photos will not help!

Shutter

Again: Less is more! If the value is too high, some areas might get overexposed and thus, the software will not be able to recognize the surface feature of the object. Here are some reference values:

- no polarizer: 5-20ms

- mostly white object,  with polarizer + one ringlight: 50-200ms

Crop X/Y

Make sure to use the right object holder to place the object in the middle of the screen. Try to crop as many unnecessary areas as possible. This will greatly lower the file size and resulting transfer and reconstruction times!

Start/Stop

Use the buttons to start/stop the routine

Reboot/Shutdown

In case of an error, try to restart the device. Always use the shutdown button before powering-off the device!

", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 760, - "y": 120, + "x": 750, + "y": 180, "wires": [ [ - "f304680180a23479" + "5fff689f9f8bc1ca" ] ] }, { - "id": "e9677b85856b5873", + "id": "654bc70a18820828", "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "Reset rfkill", - "func": "from os import system\nif \"Interface doesn't support scanning\" in msg['payload']:\n system('rfkill unblock all')\n system('ifconfig wlan0 up')\n return msg", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, - "x": 390, - "y": 1100, + "x": 660, + "y": 2720, "wires": [ [] ] }, { - "id": "91fe20cb16f54293", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1680, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, "wires": [ [ - "327c8bdde31033a4" + "e612073aded01a8f" ] ] }, { - "id": "add3e998b097c54f", + "id": "0d92559980944ae3", "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 7, + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, "width": 3, "height": 1, - "name": "rotor_anglemin", - "label": "Min Angle", + "name": "delay_after", + "label": "Delay after", "format": "", - "layout": "row-left", + "layout": "row-spread", "className": "", - "x": 780, - "y": 1680, + "x": 760, + "y": 2560, "wires": [] }, { - "id": "da286366433c83a0", + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "write", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 1680, + "x": 620, + "y": 2560, "wires": [ [] ] }, { - "id": "327c8bdde31033a4", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "rotor_anglemin", - "label": "", + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 8, - "width": 3, + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, "height": 1, "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, "className": "", - "x": 440, - "y": 1680, + "topicType": "str", + "x": 210, + "y": 980, "wires": [ [ - "da286366433c83a0" + "a7d233f984009e2e" ] ] }, { - "id": "94288df4c6756197", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "rotor_anglemax", - "label": "", - "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 10, - "width": 3, + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, "height": 1, "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, "className": "", - "x": 440, - "y": 1720, + "topicType": "str", + "x": 220, + "y": 1020, "wires": [ [ - "e531ffe3dcf34eb4" + "a7d233f984009e2e" ] ] }, { - "id": "4702a4a09124e27d", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "rotor_anglestart", - "label": "", + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", "tooltip": "", - "group": "d49a6dfd7fb17096", + "group": "8ab79a98e536e0d6", "order": 6, - "width": 3, + "width": 6, "height": 1, "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, "className": "", - "x": 440, - "y": 1760, + "topicType": "str", + "x": 240, + "y": 1060, "wires": [ [ - "9ce407cb16f0419a" + "a7d233f984009e2e" ] ] }, { - "id": "2cf946c7aab2cbb4", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1720, + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, "wires": [ [ - "94288df4c6756197" + "a7d233f984009e2e" ] ] }, { - "id": "e531ffe3dcf34eb4", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1720, - "wires": [ - [] - ] + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] }, { - "id": "4da5f650d3845baa", + "id": "a7d233f984009e2e", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 1760, + "x": 440, + "y": 980, "wires": [ [ - "4702a4a09124e27d" + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" ] ] }, { - "id": "9ce407cb16f0419a", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1760, - "wires": [ - [] - ] - }, - { - "id": "fda776c5aa642867", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 9, - "width": 3, - "height": 1, - "name": "rotor_anglemax", - "label": "Max Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1720, - "wires": [] - }, - { - "id": "6e9af48a1c4c58c6", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "d49a6dfd7fb17096", - "order": 5, - "width": 3, - "height": 1, - "name": "rotor_anglestart", - "label": "Start Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1760, - "wires": [] - }, - { - "id": "9b2bc9849aee310b", + "id": "65518f3d4e3095e5", "type": "link in", - "z": "017bd4e4a428bee5", - "name": "changeHostname", + "z": "e43a27722b508115", + "name": "link in 1", "links": [ - "ec2db55a99bbe3ee", - "d5175561293ef490", - "960912e90ba5b5bc" + "200d4b9951b6e066" ], - "x": 835, - "y": 900, + "x": 85, + "y": 980, "wires": [ [ - "8b9e3781511e9231" + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" ] ] }, { - "id": "8b9e3781511e9231", + "id": "9b851aa999e86fd7", "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "chk", - "func": "with open('/etc/hostname', 'r') as file:\n old_hostname = file.read().replace('\\n','')\nif old_hostname == 'raspberrypi':\n msg['hostname'] = 'openscan'\n msg['payload'] = 'OK'\n return msg", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", "outputs": 1, - "x": 930, - "y": 900, + "x": 670, + "y": 980, "wires": [ [ - "ebce67b739d1891f" + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" ] ] }, { - "id": "3fcbd9fe3acc3fb7", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "scan_arducam", - "group": "90223f7ddc082321", - "order": 1, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, "className": "", - "icon": "fa-question-circle", - "payload": "

Focus Settings

MF - Manual Focus

By default, the switch is 'off', which means that autofocus is active. For small objects, it might be necessary to use manual focus: activate the switch and set the focus by pressing + and - accordingly. The distance is measured between the camera lens and the focal plane (which should be in the center or slightly in front of the center of the object). Be aware, that the distance value is only a rough estimate (mm)

ST - Stacking

Stacking is disabled by default. Once activated, you will be able to set the following:

Stacksize - defines the number of photos between the minimal and the maximal focal distance

SET press this button to set the maximal/minimal focal distance. Pressing the button a third time will re-set the values.

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 760, - "y": 160, + "topic": "", + "name": "", + "x": 870, + "y": 980, "wires": [ - [ - "f304680180a23479" - ] + [] ] }, { - "id": "6d68cccec646e0a0", + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "enable routine", - "func": "msg_enable = {}\nmsg_disable = {}\n\nmsg_enable['enabled'] = True\nmsg_disable['enabled'] = False\n\nif msg['payload'] == 'external':\n return msg_enable, msg_disable\nif msg['payload'] == 'gphoto':\n return msg_enable, msg_enable, msg_disable\n\nreturn msg_enable", - "outputs": 3, - "x": 560, - "y": 440, + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, "wires": [ [ - "a0ba1aa77c5c8b7c" - ], - [ - "a42c12e94f65fa01" + "c994c779e4bad800", + "36890eb99a2ca1cf" ], - [ - "2d76e5617f13cd6c" - ] + [] ] }, { - "id": "a0ba1aa77c5c8b7c", - "type": "link out", - "z": "017bd4e4a428bee5", + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", "name": "", - "mode": "link", - "links": [ - "2aea1727dbea76ce", - "4f212b44aa487945", - "65cef204b16f8741", - "917a194be245384a" + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } ], - "x": 675, - "y": 420, - "wires": [] + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] }, { - "id": "a42c12e94f65fa01", - "type": "link out", - "z": "017bd4e4a428bee5", + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", "name": "", - "mode": "link", - "links": [ - "2aea1727dbea76ce", - "4f212b44aa487945", - "65cef204b16f8741", - "917a194be245384a" - ], - "x": 715, - "y": 440, + "x": 670, + "y": 1020, "wires": [] }, { - "id": "2d76e5617f13cd6c", - "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", - "mode": "link", - "links": [ - "65cef204b16f8741" - ], - "x": 675, - "y": 460, + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, "wires": [] }, { - "id": "bd80ec228fb9a86d", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 135, - "y": 340, + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, "wires": [ [ - "78351089ee9ebeaf", - "5fba78ae65eaaf5d", - "3492754252645e62", - "d16525a31223bc42", - "67206663b3881868" + "85ad07b8f973bbe2" ] ] }, { - "id": "65b38bfeb3fee710", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 155, - "y": 760, - "wires": [ - [ - "cc3cb10f2ea3f8b8" - ] - ] + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] }, { - "id": "d3fc91d87d5d5f62", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 135, - "y": 940, + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, "wires": [ - [ - "725fd0cab0bddc0e" - ] + [] ] }, { - "id": "cc9c4092edeb43cc", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 135, - "y": 1020, + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, "wires": [ [ - "27c6b221c90ed9e1", - "f393400.d87dcc" + "6b7245c3dcb694c8" ] ] }, { - "id": "f0b355967b33dfee", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 175, - "y": 1600, + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, "wires": [ [ - "91fe20cb16f54293", - "2cf946c7aab2cbb4", - "4da5f650d3845baa", - "6659121906897a1f", - "15f02421b30a9ab6", - "58928befcc61b1f7", - "569829eeff715c33", - "c997e60519341afd", - "59ecf3a22cd3a669", - "27bc56f273360ac7", - "9b89eb1eaf333c10", - "2e8927be0e235fa1", - "f46ced86106306c8", - "4339704cd8552eb3", - "1ac53bb6150645fe", - "0d48bb415c584420", - "b6e420121e6466e7" + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" ] ] }, { - "id": "d7c1fb4c028b21a5", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 155, - "y": 2280, + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, "wires": [ [ - "d5308090f2b7971a", - "694d1068bea15171", - "cec3e5e78a40476b", - "6f524f9370a18482", - "1f87f473e327c3cc", - "cff7ac5f1e061855", - "cf854461c37ca54f", - "ba10e04dd1761692", - "a69d216114f908a5", - "f02d4a036a225e87", - "1efd4a05aee0b86c", - "6841e5a392f0fb4f" + "253feafa5a2f8b1d" ] ] }, { - "id": "a67c18aaca2f5fa5", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 155, - "y": 2900, + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, "wires": [ - [ - "612cccacda1a65aa", - "954db931f87894ee", - "6682c8057e89d087", - "015be401d08047d2", - "1c6c0f8b9ac95659", - "dcee66c0d56c6934", - "6ec7d85bb17eb159", - "4f42d02a3776a006", - "5d70f4715c9a5ae1", - "3b126549c03a872e", - "58bbe9fc41e0d7b9" - ] + [] ] }, { - "id": "c6d3821bc7f43f8e", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Reset default", - "group": "4fe6b4c0ade0938a", - "order": 14, - "width": 6, + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, "height": 1, - "passthru": false, - "label": "Restore default settings", - "tooltip": "", - "color": "red", - "bgcolor": "", + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", "className": "", - "icon": "", - "payload": "This can not be undone!", - "payloadType": "str", - "topic": "Restore default settings?", - "topicType": "str", - "x": 930, - "y": 300, - "wires": [ - [ - "e4be21c38b57f560" - ] - ] + "x": 800, + "y": 1940, + "wires": [] }, { - "id": "e4be21c38b57f560", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1090, - "y": 300, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, "wires": [ [ - "9f30de04ced693d3" + "69516440e3997111", + "f036424d79645761" ] ] }, { - "id": "9f30de04ced693d3", + "id": "d54b85891248ba88", "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 1230, - "y": 300, + "x": 270, + "y": 440, "wires": [ [ - "80bccc884b0be297" + "eefed04c25e3e4d6" ] ] }, { - "id": "80bccc884b0be297", - "type": "link out", - "z": "017bd4e4a428bee5", + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 1325, - "y": 300, - "wires": [] - }, - { - "id": "34b685aff2080d31", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "boot-cam", - "func": "from OpenScan import load_str\n\ncamera_modules = ('imx519', 'imx219', 'ov5647', 'imx477', 'imx378', 'ov9281', 'imx290a', 'imx290b')\n\npt1 = \"[all]\\n\\ncamera_auto_detect=0\\ngpu_mem=256\\ndtoverlay=vc4-fkms-v3d\\ndtoverlay=\"\npt3 = \",media-controller=1\\n\"\n\nwith open('/boot/config.txt', 'r') as file:\n config = file.read()\n\ncamera = load_str('camera')\nif camera not in camera_modules:\n msg['payload'] = 'no changes'\n return\n\nif camera == 'imx290a':\n camera = 'imx290,clock-frequency=37125000'\nelif camera == 'imx290b':\n camera = 'imx290,clock-frequency=74250000'\n\nconfig_keep = config.split('[all]\\n')[0]\nconfig_new = config_keep + pt1 + camera + pt3\n\nwith open('/boot/config.txt', 'w') as file:\n file.write(config_new)\n\nmsg['topic'] = 'Camera configuration changed'\nmsg['payload'] = 'Please restart the device'\n\nreturn msg", - "outputs": 1, - "x": 680, - "y": 500, + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, "wires": [ [ - "68cba0c530c6def6" + "2aaf7c7f0f0c146f" ] ] }, { - "id": "68cba0c530c6def6", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 830, - "y": 500, + "x": 660, + "y": 440, "wires": [ [] ] }, { - "id": "f304680180a23479", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": true, - "className": "", - "topic": "", - "name": "Info", - "x": 1010, - "y": 120, - "wires": [ - [] - ] + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] }, { - "id": "0d48bb415c584420", + "id": "a12ead9ccf239c19", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'turntable_mode'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 1640, + "x": 190, + "y": 3560, "wires": [ [ - "ce215e159ce7267f" + "d0a1a4947a1137ca" ] ] }, { - "id": "ce215e159ce7267f", + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", "type": "ui_switch", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Turntable Mode", - "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 2, - "width": 6, - "height": 1, + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", "passthru": true, "decouple": "false", - "topic": "", - "topicType": "str", + "topic": "topic", + "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", @@ -9788,179 +8386,286 @@ "offcolor": "", "animate": false, "className": "", - "x": 440, - "y": 1640, + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, "wires": [ [ - "f95f528dec31425c" + "1c08a329bd2a669c" ] ] }, { - "id": "f95f528dec31425c", + "id": "bf8e971a52cddab1", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'turntable_mode'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 1640, + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, "wires": [ [] ] }, { - "id": "4ebe5baece5ce9f2", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "preview_resolution", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", "order": 5, - "width": 3, + "width": 6, "height": 1, "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.5", - "max": "10", - "step": "0.5", + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, "className": "", - "x": 450, - "y": 2280, + "topicType": "msg", + "x": 350, + "y": 3640, "wires": [ [ - "60a415fff23cb55e" + "b5aba11033c5f952" ] ] }, { - "id": "9ed0498cceceedde", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 4, - "width": 3, - "height": 1, - "name": "preview_res", - "label": "Preview Resolution (Mpx)", - "format": "", - "layout": "row-spread", + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, +{ + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", "x": 770, - "y": 2280, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, +{ + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, +{ + "id": "69885a9ce218eb71", + "type": "comment", + "z": "e43a27722b508115", + "name": "Coloritos", + "info": "", + "x": 100, + "y": 3740, "wires": [] }, { - "id": "1efd4a05aee0b86c", + "id": "dc1cde67c3022e6b", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'cam_preview_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data)/1000000;\nreturn msg", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2280, + "x": 190, + "y": 3800, "wires": [ [ - "4ebe5baece5ce9f2" + "0dccca85770c7936" ] ] }, { - "id": "60a415fff23cb55e", + "id": "b63e8246ad14ad9d", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_preview_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload*1000000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "interface-color", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 2280, + "x": 540, + "y": 3800, "wires": [ [] ] }, { - "id": "6f3d403e157163e4", + "id": "b7044aa75196b521", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3800, + "wires": [ + [ + "dc1cde67c3022e6b" + ] + ] + }, + { + "id": "0dccca85770c7936", "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "Camera", + "z": "e43a27722b508115", + "name": "interface_color", "label": "", "tooltip": "", "place": "Select option", - "group": "1f7f7e1e24f5ad9b", - "order": 5, - "width": 4, - "height": 1, + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, "passthru": true, "multiple": false, "options": [ { - "label": "Pi Cam v1 - 5mp", - "value": "ov5647", + "label": "Aburrido", + "value": "#097479", "type": "str" }, { - "label": "Pi Cam v2 - 8mp", - "value": "imx219", + "label": "Morado", + "value": "#790974", "type": "str" }, { - "label": "Pi Cam HQ - 12.3mp", - "value": "imx477", + "label": "Berenjena", + "value": "#79093c", "type": "str" }, { - "label": "Arducam IMX519 - 16mp", - "value": "imx519", + "label": "Azul", + "value": "#093c79 ", "type": "str" }, { - "label": "IMX290 a", - "value": "imx290a", - "type": "str" - }, - { - "label": "IMX290 b", - "value": "imx290b", - "type": "str" - }, - { - "label": "IMX378", - "value": "imx378", - "type": "str" - }, - { - "label": "OV9281", - "value": "ov9281", - "type": "str" - }, - { - "label": "DSLR (gphoto)", - "value": "gphoto", - "type": "str" - }, - { - "label": "USB Webcam", - "value": "usb_webcam", - "type": "str" - }, - { - "label": "External Camera", - "value": "external", + "label": "Oliva", + "value": "#747909", "type": "str" } ], @@ -9968,154 +8673,133 @@ "topic": "topic", "topicType": "msg", "className": "", - "x": 400, - "y": 460, + "x": 360, + "y": 3800, "wires": [ [ - "6d68cccec646e0a0", - "4058a31e942e8f95" + "b63e8246ad14ad9d" ] ] }, - { - "id": "c6138801b30f091d", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "model", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "1f7f7e1e24f5ad9b", - "order": 3, - "width": 4, - "height": 1, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Please Select", - "value": "None", - "type": "str" - }, - { - "label": "OpenScan Mini", - "value": "OSMini", - "type": "str" - }, - { - "label": "OpenScan Classic", - "value": "OSClassic", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 390, - "y": 580, +{ + "id": "667950f6671bd1a0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 840, "wires": [ [ - "896242c5a7e50fa7" + "b82a1cbefad51cd8" ] ] }, { - "id": "4da67c23c7a543a0", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "1f7f7e1e24f5ad9b", - "order": 4, - "width": 2, - "height": 1, - "name": "", - "label": "Camera", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 840, - "y": 460, - "wires": [] - }, - { - "id": "1fed8676078ea9a7", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "1f7f7e1e24f5ad9b", - "order": 2, - "width": 2, - "height": 1, - "name": "", - "label": "Model", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 580, - "wires": [] - }, - { - "id": "a4b7eea9a9736b0a", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Update&Info", - "group": "ddbd496e.93a288", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", + "id": "5f32d7e78e368454", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "b82a1cbefad51cd8", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "hostname", + "label": "Hostname", "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", - "payloadType": "str", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "mode": "text", + "delay": 300, "topic": "topic", + "sendOnBlur": true, + "className": "", "topicType": "msg", - "x": 750, - "y": 200, + "x": 360, + "y": 840, "wires": [ [ - "f304680180a23479" + "5f32d7e78e368454" ] ] }, +{ + "id": "5fd155711e29b1b8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Monitoring", + "info": "", + "x": 100, + "y": 3860, + "wires": [] + }, { - "id": "b6e420121e6466e7", + "id": "815702499384f118", "type": "function", - "z": "017bd4e4a428bee5", + "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'routine_secondpass'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 1600, + "x": 190, + "y": 3920, "wires": [ [ - "ab8d5cfe9190bb5f" + "bfdbdae28bf42ed4" ] ] }, { - "id": "ab8d5cfe9190bb5f", + "id": "464c8495f86daaa7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "datadog_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3920, + "wires": [ + [] + ] + }, + { + "id": "bfdbdae28bf42ed4", "type": "ui_switch", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Second pass", - "tooltip": "", - "group": "d49a6dfd7fb17096", - "order": 3, - "width": 6, - "height": 1, + "z": "e43a27722b508115", + "name": "datadog_enable", + "label": "Enable Datadog", + "tooltip": "Enable Datadog monitoring", + "group": "33aff36289823faa", + "order": 1, + "width": "6", + "height": "1", "passthru": true, "decouple": "false", - "topic": "", - "topicType": "str", + "topic": "topic", + "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", @@ -10127,206 +8811,261 @@ "offcolor": "", "animate": false, "className": "", - "x": 430, - "y": 1600, + "x": 340, + "y": 3920, + "wires": [ + [ + "464c8495f86daaa7" + ] + ] + }, + { + "id": "f93ce2d26953341f", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "label": "Datadog Api Token", + "tooltip": "Datadog Api Token", + "group": "33aff36289823faa", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3960, "wires": [ [ - "fa51327f0140b045" + "647641e79884eb87" ] ] }, { - "id": "fa51327f0140b045", + "id": "ee668e39d213070b", "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'routine_secondpass'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 1600, + "x": 190, + "y": 3960, "wires": [ - [] + [ + "f93ce2d26953341f" + ] ] }, { - "id": "6841e5a392f0fb4f", + "id": "647641e79884eb87", "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'cam_output_downscale'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 290, - "y": 2320, + "x": 550, + "y": 3960, + "wires": [ + [] + ] + }, + { + "id": "ff2dea1ab9cb7776", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 4", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3960, "wires": [ [ - "110216d678fad14f" + "815702499384f118", + "ee668e39d213070b" ] ] }, - { - "id": "110216d678fad14f", - "type": "ui_switch", - "z": "017bd4e4a428bee5", +{ + "id": "a1b81e7fe94ad4e5", + "type": "python3-function", + "z": "e43a27722b508115", "name": "", - "label": "Downscale output", + "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", + "outputs": 1, + "x": 530, + "y": 3740, + "wires": [ + [] + ] + }, + { + "id": "2f3a3c0e682ae862", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "restart_interface", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "Restart Interface", "tooltip": "", - "group": "93aadb71dee6d977", - "order": 6, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, + "color": "", + "bgcolor": "", "className": "", - "x": 450, - "y": 2320, + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 340, + "y": 3740, "wires": [ [ - "214d548d564f8ba2" + "a1b81e7fe94ad4e5" ] ] }, { - "id": "214d548d564f8ba2", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_output_downscale'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n \nmsg.enabled = msg.payload\nreturn msg", + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2320, + "x": 260, + "y": 140, "wires": [ [ - "1becbff4884b8c1a" + "e23c514008cad1a1" ] ] }, { - "id": "8be1ca844a6caa54", - "type": "ui_slider", - "z": "017bd4e4a428bee5", - "name": "output_resolution", - "label": "", - "tooltip": "", - "group": "93aadb71dee6d977", - "order": 8, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, "topic": "", - "topicType": "str", - "min": "0.5", - "max": "20", - "step": "0.5", - "className": "", - "x": 450, - "y": 2360, + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, "wires": [ [ - "a6b2c0a0604ccf14" + "4c7fa5b5b27b83a5" ] ] }, { - "id": "9ac09d89d791e953", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "93aadb71dee6d977", - "order": 7, - "width": 3, - "height": 1, - "name": "image_res", - "label": "Output Resolution (Mpx)", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2360, - "wires": [] - }, - { - "id": "1becbff4884b8c1a", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadI", - "func": "var file = 'cam_output_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data)/1000000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2360, + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, "wires": [ [ - "8be1ca844a6caa54" + "4ce127c61c3c5966", + "beacc3dc5398fa79" ] ] }, { - "id": "a6b2c0a0604ccf14", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'cam_output_resolution'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload*1000000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2360, + "x": 290, + "y": 220, "wires": [ [] ] }, { - "id": "f358de1e64b491bb", + "id": "beacc3dc5398fa79", "type": "link out", - "z": "017bd4e4a428bee5", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ - "b30d918661392ab3", - "44c598049cd533fd" + "38783aea9cc317a6" ], - "x": 635, - "y": 620, + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, "wires": [] }, { "id": "b0629875a30ae1d7", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", "outputs": 2, - "x": 350, - "y": 240, + "x": 390, + "y": 540, "wires": [ [ "1bbe2d769f42c313" @@ -10339,7 +9078,7 @@ { "id": "c7b6d05a62172432", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "ddbd496e.93a288", "order": 3, "width": 0, @@ -10349,19 +9088,19 @@ "format": "{{msg.status}}", "layout": "row-spread", "className": "", - "x": 170, - "y": 100, + "x": 210, + "y": 400, "wires": [] }, { "id": "fefe45404bdb19c4", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, - "x": 510, - "y": 260, + "x": 550, + "y": 560, "wires": [ [ "1bbe2d769f42c313", @@ -10372,13 +9111,14 @@ { "id": "d0104e0163745993", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "links": [ - "960912e90ba5b5bc" + "960912e90ba5b5bc", + "50eeb3e362f9027f" ], - "x": 75, - "y": 140, + "x": 115, + "y": 440, "wires": [ [ "ec30638407332e43", @@ -10390,7 +9130,7 @@ { "id": "ec30638407332e43", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "loadS", "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", "outputs": 1, @@ -10398,8 +9138,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 180, + "x": 210, + "y": 480, "wires": [ [ "2852023f3aa8db10" @@ -10409,7 +9149,7 @@ { "id": "2852023f3aa8db10", "type": "ui_dropdown", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "label": "", "tooltip": "", @@ -10423,21 +9163,26 @@ "options": [ { "label": "stable", - "value": "main", + "value": "stable", "type": "str" }, { "label": "beta", "value": "beta", "type": "str" - } + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", - "x": 300, - "y": 180, + "x": 340, + "y": 480, "wires": [ [ "1e10b387ee30c486" @@ -10447,7 +9192,7 @@ { "id": "1e10b387ee30c486", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "write", "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -10455,8 +9200,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 430, - "y": 180, + "x": 470, + "y": 480, "wires": [ [] ] @@ -10464,7 +9209,7 @@ { "id": "274129c51b0b87ef", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "ddbd496e.93a288", "order": 4, "width": 4, @@ -10474,14 +9219,14 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 570, - "y": 180, + "x": 610, + "y": 480, "wires": [] }, { "id": "51cd8c8643e6b46a", "type": "ui_switch", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "label": "Auto-check update availability", "tooltip": "", @@ -10504,8 +9249,8 @@ "offcolor": "", "animate": false, "className": "", - "x": 370, - "y": 140, + "x": 410, + "y": 440, "wires": [ [ "1ab4c6b4b232a022" @@ -10515,7 +9260,7 @@ { "id": "38cbf7965d1c1834", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "loadB", "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, @@ -10523,8 +9268,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 140, + "x": 210, + "y": 440, "wires": [ [ "51cd8c8643e6b46a" @@ -10534,7 +9279,7 @@ { "id": "1ab4c6b4b232a022", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "write", "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, @@ -10542,8 +9287,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 570, - "y": 140, + "x": 610, + "y": 440, "wires": [ [] ] @@ -10551,7 +9296,7 @@ { "id": "ae92a328af306ebb", "type": "ui_toast", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "position": "dialog", "displayTime": "3", "highlight": "", @@ -10563,8 +9308,8 @@ "className": "", "topic": "", "name": "", - "x": 670, - "y": 260, + "x": 710, + "y": 560, "wires": [ [ "2de63e8e3ae5fb0c", @@ -10575,14 +9320,14 @@ { "id": "cbd0afc4aa7b302a", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "update status", "links": [ "1bbe2d769f42c313", "42061b28cff81f99" ], - "x": 75, - "y": 100, + "x": 115, + "y": 400, "wires": [ [ "c7b6d05a62172432", @@ -10593,20 +9338,20 @@ { "id": "1bbe2d769f42c313", "type": "link out", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ "cbd0afc4aa7b302a" ], - "x": 625, - "y": 220, + "x": 665, + "y": 520, "wires": [] }, { "id": "7cf60615d93e696b", "type": "ui_button", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", "order": 7, @@ -10623,8 +9368,8 @@ "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 140, - "y": 260, + "x": 180, + "y": 560, "wires": [ [ "b0629875a30ae1d7" @@ -10634,12 +9379,12 @@ { "id": "2de63e8e3ae5fb0c", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, - "x": 840, - "y": 260, + "x": 880, + "y": 560, "wires": [ [ "42061b28cff81f99", @@ -10650,7 +9395,7 @@ { "id": "929281fef53e09f8", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "msg", "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", "outputs": 1, @@ -10658,8 +9403,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 810, - "y": 220, + "x": 850, + "y": 520, "wires": [ [ "42061b28cff81f99" @@ -10669,20 +9414,20 @@ { "id": "42061b28cff81f99", "type": "link out", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ "cbd0afc4aa7b302a" ], - "x": 955, - "y": 220, + "x": 995, + "y": 520, "wires": [] }, { "id": "49f1ecb29a3f84f4", "type": "function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "loadB", "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", "outputs": 1, @@ -10690,8 +9435,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 170, - "y": 220, + "x": 210, + "y": 520, "wires": [ [ "b0629875a30ae1d7" @@ -10701,26 +9446,28 @@ { "id": "fe3a855fee9e28c6", "type": "link out", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "mode": "link", "links": [ - "9bb0adbd716ce347" + "9bb0adbd716ce347", + "01c882fcc51b349c" ], - "x": 955, - "y": 260, + "x": 995, + "y": 560, "wires": [] }, { "id": "5e7d5e4335d37794", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "links": [ - "960912e90ba5b5bc" + "960912e90ba5b5bc", + "50eeb3e362f9027f" ], - "x": 55, - "y": 400, + "x": 95, + "y": 700, "wires": [ [ "2bb5fe78e09fec8a" @@ -10730,12 +9477,12 @@ { "id": "2bb5fe78e09fec8a", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "msg", "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", "outputs": 1, - "x": 170, - "y": 400, + "x": 210, + "y": 700, "wires": [ [ "dbc77052ac950624", @@ -10753,7 +9500,7 @@ { "id": "d97c3068ef5fef96", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 2, "width": 0, @@ -10763,14 +9510,14 @@ "format": "{{msg.os}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 440, + "x": 490, + "y": 740, "wires": [] }, { "id": "73a3b828f862312b", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 8, "width": 0, @@ -10780,14 +9527,14 @@ "format": "{{msg.flask}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 480, + "x": 490, + "y": 780, "wires": [] }, { "id": "dbc77052ac950624", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 1, "width": 0, @@ -10797,61 +9544,17 @@ "format": "{{msg.device}}", "layout": "row-spread", "className": "", - "x": 460, - "y": 400, + "x": 500, + "y": 700, "wires": [] }, - { - "id": "4c7fa5b5b27b83a5", - "type": "python3-function", - "z": "c8e7ecb5849edb9a", - "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\nscope = 'main'\n#scope = 'beta'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\ndel msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/Arducam.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/Arducam.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/OpenScan.py'\nmsg[scope]['3']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/config.txt'\nmsg[scope]['4']['dst'] = '/boot/config.txt'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/flows.json'\nmsg[scope]['5']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['6'] = {}\nmsg[scope]['6']['src'] = scope + '/settings.js'\nmsg[scope]['6']['dst'] = '/root/.node-red/settings.js'\n\nmsg[scope]['7'] = {}\nmsg[scope]['7']['src'] = 'files/logo.jpg'\nmsg[scope]['7']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", - "outputs": 1, - "x": 300, - "y": 820, - "wires": [ - [ - "50f6fb8adf0249d7" - ] - ] - }, - { - "id": "80175eb8dc6ad009", - "type": "inject", - "z": "c8e7ecb5849edb9a", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 140, - "y": 820, - "wires": [ - [ - "4c7fa5b5b27b83a5" - ] - ] - }, { "id": "3f42560297fe6978", "type": "ui_template", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "name": "Download LOG", - "order": 9, + "order": 10, "width": 6, "height": 1, "format": "\n
Download error log\n
\n", @@ -10860,8 +9563,8 @@ "resendOnRefresh": false, "templateScope": "local", "className": "", - "x": 140, - "y": 760, + "x": 180, + "y": 1060, "wires": [ [] ] @@ -10869,12 +9572,12 @@ { "id": "c94623ddd9d95f78", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "get update", "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", "outputs": 1, - "x": 170, - "y": 60, + "x": 210, + "y": 360, "wires": [ [] ] @@ -10882,13 +9585,13 @@ { "id": "39a502b38837273d", "type": "link in", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "links": [ "1e7457ea9c2c5e09" ], - "x": 205, - "y": 300, + "x": 245, + "y": 600, "wires": [ [ "b0629875a30ae1d7" @@ -10898,7 +9601,7 @@ { "id": "901e31453b2bdff8", "type": "delay", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "pauseType": "delay", "timeout": "10", @@ -10912,8 +9615,8 @@ "drop": false, "allowrate": false, "outputs": 1, - "x": 180, - "y": 440, + "x": 220, + "y": 740, "wires": [ [ "2bb5fe78e09fec8a" @@ -10923,7 +9626,7 @@ { "id": "f983854748ee4763", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 3, "width": 0, @@ -10933,14 +9636,14 @@ "format": "{{msg.osdate}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 520, + "x": 490, + "y": 820, "wires": [] }, { "id": "5347c7c517f5e8c7", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 4, "width": 0, @@ -10950,14 +9653,14 @@ "format": "{{msg.temp}}", "layout": "row-spread", "className": "", - "x": 470, - "y": 560, + "x": 510, + "y": 860, "wires": [] }, { "id": "3a5016f7003cd72c", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 5, "width": 0, @@ -10967,14 +9670,14 @@ "format": "{{msg.cpu}}", "layout": "row-spread", "className": "", - "x": 480, - "y": 600, + "x": 520, + "y": 900, "wires": [] }, { "id": "6d720c4a4ecd9475", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 6, "width": 0, @@ -10984,14 +9687,14 @@ "format": "{{msg.swap}}", "layout": "row-spread", "className": "", - "x": 480, - "y": 640, + "x": 520, + "y": 940, "wires": [] }, { "id": "6438b7d060a70d81", "type": "ui_text", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "order": 7, "width": 0, @@ -11001,70 +9704,14 @@ "format": "{{msg.diskspace}}", "layout": "row-spread", "className": "", - "x": 470, - "y": 680, - "wires": [] - }, - { - "id": "d7362e6e0ec7bdaa", - "type": "inject", - "z": "c8e7ecb5849edb9a", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 130, - "y": 900, - "wires": [ - [ - "4ce127c61c3c5966", - "beacc3dc5398fa79" - ] - ] - }, - { - "id": "4ce127c61c3c5966", - "type": "python3-function", - "z": "c8e7ecb5849edb9a", - "name": "prepare image creation", - "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", - "outputs": 1, - "x": 330, - "y": 900, - "wires": [ - [] - ] - }, - { - "id": "beacc3dc5398fa79", - "type": "link out", - "z": "c8e7ecb5849edb9a", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 235, - "y": 940, + "x": 510, + "y": 980, "wires": [] }, { "id": "8d012912f302be85", "type": "ui_button", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", "order": 8, @@ -11081,8 +9728,8 @@ "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 170, - "y": 340, + "x": 210, + "y": 640, "wires": [ [ "5242607a723cc628" @@ -11092,12 +9739,12 @@ { "id": "5242607a723cc628", "type": "python3-function", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "name": "Changelog", - "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/OpenScan-org/OpenScan-Doc/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", "outputs": 1, - "x": 390, - "y": 340, + "x": 430, + "y": 640, "wires": [ [ "573722197b15bf84" @@ -11107,7 +9754,7 @@ { "id": "573722197b15bf84", "type": "ui_toast", - "z": "c8e7ecb5849edb9a", + "z": "a5557543ccff5889", "position": "dialog", "displayTime": "3", "highlight": "", @@ -11119,27 +9766,51 @@ "className": "", "topic": "", "name": "", - "x": 570, - "y": 340, + "x": 610, + "y": 640, "wires": [ [] ] }, { - "id": "50f6fb8adf0249d7", - "type": "debug", - "z": "c8e7ecb5849edb9a", + "id": "cde61b7de9eeaba7", + "type": "ui_button", + "z": "a5557543ccff5889", "name": "", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 570, - "y": 820, - "wires": [] + "group": "3ce32450.e0cffc", + "order": 9, + "width": 0, + "height": 0, + "passthru": false, + "label": "Expand Root", + "tooltip": "Sets the maximum space your SD card admits", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "expand", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 510, + "y": 1020, + "wires": [ + [ + "eab36487d201f867" + ] + ] + }, + { + "id": "eab36487d201f867", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "", + "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", + "outputs": 1, + "x": 690, + "y": 1020, + "wires": [ + [] + ] } ] \ No newline at end of file diff --git a/update/betaArdu/settings.js b/update/meanwhile/settings.js similarity index 99% rename from update/betaArdu/settings.js rename to update/meanwhile/settings.js index 41082c3..357b02b 100644 --- a/update/betaArdu/settings.js +++ b/update/meanwhile/settings.js @@ -19,6 +19,7 @@ * - Node Settings * **/ +process.env.HOSTNAME = require('os').hostname(); module.exports = { diff --git a/update/meanwhile/start.sh b/update/meanwhile/start.sh new file mode 100755 index 0000000..223ffae --- /dev/null +++ b/update/meanwhile/start.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +flow_dir="/home/pi/OpenScan/settings/.node-red" +flows_json_file=$flow_dir/flows.json +hostname=`hostname` +echo $hostname +session_token=`echo $RANDOM | md5sum | head -c 20` +echo $session_token > /home/pi/OpenScan/settings/session_token + +cat $flow_dir/flows.json.tmpl|sed "s|{{ hostname }}|$hostname.local|g" > $flows_json_file +sed -i "s|{{ session_token }}|$session_token|g" $flows_json_file diff --git a/update/mini/Arducam.py b/update/mini/Arducam.py deleted file mode 100644 index 941e07b..0000000 --- a/update/mini/Arducam.py +++ /dev/null @@ -1,202 +0,0 @@ -import time -import os - -try: - import v4l2 -except Exception as e: - print(e) - print("Try to install v4l2-fix") - try: - from pip import main as pipmain - except ImportError: - from pip._internal import main as pipmain - pipmain(['install', 'v4l2-fix']) - print("\nTry to run the focus program again.") - exit(0) - -import fcntl -import errno - -# # Type -# v4l2.V4L2_CTRL_TYPE_INTEGER -# v4l2.V4L2_CTRL_TYPE_BOOLEAN -# v4l2.V4L2_CTRL_TYPE_MENU -# v4l2.V4L2_CTRL_TYPE_BUTTON -# v4l2.V4L2_CTRL_TYPE_INTEGER64 -# v4l2.V4L2_CTRL_TYPE_CTRL_CLASS -# # Flags -# v4l2.V4L2_CTRL_FLAG_DISABLED -# v4l2.V4L2_CTRL_FLAG_GRABBED -# v4l2.V4L2_CTRL_FLAG_READ_ONLY -# v4l2.V4L2_CTRL_FLAG_UPDATE -# v4l2.V4L2_CTRL_FLAG_INACTIVE -# v4l2.V4L2_CTRL_FLAG_SLIDER - -def assert_valid_queryctrl(queryctrl): - return queryctrl.type & ( - v4l2.V4L2_CTRL_TYPE_INTEGER - | v4l2.V4L2_CTRL_TYPE_BOOLEAN - | v4l2.V4L2_CTRL_TYPE_MENU - | v4l2.V4L2_CTRL_TYPE_BUTTON - | v4l2.V4L2_CTRL_TYPE_INTEGER64 - | v4l2.V4L2_CTRL_TYPE_CTRL_CLASS - | 7 - | 8 - | 9 - ) and queryctrl.flags & ( - v4l2.V4L2_CTRL_FLAG_DISABLED - | v4l2.V4L2_CTRL_FLAG_GRABBED - | v4l2.V4L2_CTRL_FLAG_READ_ONLY - | v4l2.V4L2_CTRL_FLAG_UPDATE - | v4l2.V4L2_CTRL_FLAG_INACTIVE - | v4l2.V4L2_CTRL_FLAG_SLIDER - ) - -def get_device_controls_menu(fd, queryctrl): - querymenu = v4l2.v4l2_querymenu(queryctrl.id, queryctrl.minimum) - while querymenu.index <= queryctrl.maximum: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYMENU, querymenu) - yield querymenu - querymenu.index += 1 - -def get_device_controls_by_class(fd, control_class): - # enumeration by control class - queryctrl = v4l2.v4l2_queryctrl(control_class | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) - while True: - try: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) - except IOError as e: - assert e.errno == errno.EINVAL - break - if v4l2.V4L2_CTRL_ID2CLASS(queryctrl.id) != control_class: - break - yield queryctrl - queryctrl = v4l2.v4l2_queryctrl(queryctrl.id | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) - -def getdict(struct): - val = dict((field, getattr(struct, field)) for field, _ in struct._fields_) - val.pop("reserved") - return val - -def get_device_controls(fd): - # original enumeration method - queryctrl = v4l2.v4l2_queryctrl(v4l2.V4L2_CID_BASE) - while queryctrl.id < v4l2.V4L2_CID_LASTP1: - try: - fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) - print(queryctrl.name) - except IOError as e: - # this predefined control is not supported by this device - assert e.errno == errno.EINVAL - queryctrl.id += 1 - continue - queryctrl = v4l2.v4l2_queryctrl(queryctrl.id + 1) - -def get_ctrls(vd): - ctrls = [] - # enumeration by control class - for class_ in (v4l2.V4L2_CTRL_CLASS_USER, v4l2.V4L2_CTRL_CLASS_MPEG, v4l2.V4L2_CTRL_CLASS_CAMERA): - for queryctrl in get_device_controls_by_class(vd, class_): - ctrl = getdict(queryctrl) - if queryctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: - ctrl["menu"] = [] - for querymenu in get_device_controls_menu(vd, queryctrl): - # print(querymenu.name) - ctrl["menu"].append(querymenu.name) - - if queryctrl.type == 9: - ctrl["menu"] = [] - for querymenu in get_device_controls_menu(vd, queryctrl): - ctrl["menu"].append(querymenu.index) - ctrls.append(ctrl) - return ctrls - -def set_ctrl(vd, id, value): - ctrl = v4l2.v4l2_control() - ctrl.id = id - ctrl.value = value - try: - fcntl.ioctl(vd, v4l2.VIDIOC_S_CTRL, ctrl) - except IOError as e: - print(e) - -def get_ctrl(vd, id): - ctrl = v4l2.v4l2_control() - ctrl.id = id - try: - fcntl.ioctl(vd, v4l2.VIDIOC_G_CTRL, ctrl) - except IOError as e: - print(e) - return None - return ctrl.value - - -class Focuser: - FOCUS_ID = 0x009a090a - dev = None - - def __init__(self, dev=0): - self.focus_value = 0 - self.dev = dev - - if type(dev) == int or (type(dev) == str and dev.isnumeric()): - self.dev = "/dev/video{}".format(dev) - - self.fd = open(self.dev, 'r') - self.ctrls = get_ctrls(self.fd) - self.hasFocus = False - for ctrl in self.ctrls: - if ctrl['id'] == Focuser.FOCUS_ID: - self.hasFocus = True - self.opts[Focuser.OPT_FOCUS]["MIN_VALUE"] = ctrl['minimum'] - self.opts[Focuser.OPT_FOCUS]["MAX_VALUE"] = ctrl['maximum'] - self.opts[Focuser.OPT_FOCUS]["DEF_VALUE"] = ctrl['default'] - self.focus_value = get_ctrl(self.fd, Focuser.FOCUS_ID) - - if not self.hasFocus: - raise RuntimeError("Device {} has no focus_absolute control.".format(self.dev)) - - def read(self): - return self.focus_value - - def write(self, value): - self.focus_value = value - # os.system("v4l2-ctl -d {} -c focus_absolute={}".format(self.dev, value)) - set_ctrl(self.fd, Focuser.FOCUS_ID, value) - - OPT_BASE = 0x1000 - OPT_FOCUS = OPT_BASE | 0x01 - OPT_ZOOM = OPT_BASE | 0x02 - OPT_MOTOR_X = OPT_BASE | 0x03 - OPT_MOTOR_Y = OPT_BASE | 0x04 - OPT_IRCUT = OPT_BASE | 0x05 - opts = { - OPT_FOCUS : { - "MIN_VALUE": 0, - "MAX_VALUE": 1000, - "DEF_VALUE": 0, - }, - } - def reset(self,opt,flag = 1): - info = self.opts[opt] - if info == None or info["DEF_VALUE"] == None: - return - self.set(opt,info["DEF_VALUE"]) - - def get(self,opt,flag = 0): - info = self.opts[opt] - return self.read() - - def set(self,opt,value,flag = 1): - info = self.opts[opt] - if value > info["MAX_VALUE"]: - value = info["MAX_VALUE"] - elif value < info["MIN_VALUE"]: - value = info["MIN_VALUE"] - self.write(value) - print("write: {}".format(value)) - - def __del__(self): - self.fd.close() - -pass diff --git a/update/mini/OpenScan.py b/update/mini/OpenScan.py deleted file mode 100644 index 5837204..0000000 --- a/update/mini/OpenScan.py +++ /dev/null @@ -1,185 +0,0 @@ -basepath = '/home/pi/OpenScan/' -from os.path import isfile - -def load_bool(name): - filename = basepath+'settings/'+name - if not isfile(filename): - return - with open(filename, 'r') as file: - value = file.read().replace('\n','') - if value == '1' or value == 'True' or value =='true': - value = True - else: - value = False - return value - -def load_str(name): - filename = basepath+'settings/'+name - if not isfile(filename): - return - with open(filename, 'r') as file: - value = file.read().replace('\n','') - return value - -def load_int(name): - filename = basepath+'settings/'+name - if not isfile(filename): - return - with open(filename, 'r') as file: - value = int(file.read().replace('\n','')) - return value - -def load_float(name): - filename = basepath+'settings/'+name - if not isfile(filename): - return - with open(filename, 'r') as file: - value = float(file.read().replace('\n','')) - return value - -def save(name, value): - filename = basepath+'settings/'+name - with open(filename, 'w+') as file: - file.write(str(value)) - return - -def OpenScanCloud(cmd, msg): - from requests import get - osc_user = 'openscan' - osc_pw = 'free' - osc_server = 'http://openscanfeedback.dnsuser.de:1334/' - - try: - r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) - except: - r = type('obj', (object,), {'status_code' : 404, 'text':None}) - return r - -def camera(cmd, msg = {}): - from requests import get - flask = 'http://127.0.0.1:1312/' - r = get(flask + cmd, params=msg) - return r.status_code - -def motorrun(motor,angle): - import RPi.GPIO as GPIO - from time import sleep - from math import cos - GPIO.setwarnings(False) - GPIO.setmode(GPIO.BCM) - - spr = load_int(motor + '_stepsperrotation') - dirpin = load_int('pin_' + motor + '_dir') - steppin = load_int('pin_' + motor +'_step') - dir = load_int(motor + '_dir') - ramp = load_int(motor + '_accramp') - acc = load_float(motor + '_acc') - delay_init = load_float(motor + '_delay') - delay = delay_init - - step_count=int(angle*spr/360) * dir - GPIO.setup(dirpin, GPIO.OUT) - GPIO.setup(steppin, GPIO.OUT) - if (step_count>0): - GPIO.output(dirpin, GPIO.HIGH) - if(step_count<0): - GPIO.output(dirpin, GPIO.LOW) - step_count=-step_count - for x in range(step_count): - GPIO.output(steppin, GPIO.HIGH) - if x<=ramp and x<=step_count/2: - delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) - #delay=delay_init+(ramp-x)*(delay_init)/acc - elif step_count-x<=ramp and x>step_count/2: - delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) - #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc - else: - delay = delay_init - sleep(delay) - GPIO.output(steppin, GPIO.LOW) - sleep(delay) - -def ringlight(number,state): - import RPi.GPIO as GPIO - pin = load_int('pin_ringlight' + str(number)) - GPIO.setwarnings(False) - GPIO.setmode(GPIO.BCM) - GPIO.setup(pin, GPIO.OUT) - GPIO.output(pin, state) - -def take_photo(file): - from os import system - filepath = basepath + file - - model=load_str('model') - - - - shutter = str(load_int('cam_shutter')) - saturation = load_str('cam_saturation') - contrast = load_str('cam_contrast') - awbg_red = load_str('cam_awbg_red') - awbg_blue = load_str('cam_awbg_blue') - gain = load_str('cam_gain') - quality = load_int('cam_jpeg_quality') - filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' - #width = load_str('cam_resx') - #height = load_str('cam_resy') - timeout = load_str('cam_timeout') - cropx = load_int('cam_cropx')/200 - cropy = load_int('cam_cropy')/200 - rotation = load_int('cam_rotation') - AF = load_bool('cam_AFmode') - camera = load_str('camera') - - - if camera == 'imx519' and AF == True: - autofocus = ' --autofocus ' - else: - autofocus = '' - - cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' -# cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus - system(cmd) - return cmd - -def get_points(samples=1): - from math import pi, sqrt, acos, atan2, cos, sin - - points = [] - phi = pi * (3. - sqrt(5.)) - for i in range(int(samples)): - y = 1 - (i / float(samples - 1)) * 2 - radius = sqrt(1 - y * y) - theta = phi * i - x = cos(theta) * radius - z = sin(theta) * radius - r=sqrt(x*x+y*y+z*z) - theta_neu=acos(z/r)*180/pi - phi_neu=atan2(y,x)*180/pi - points.append((theta_neu-90,phi_neu)) - points.sort() - return points - -def create_coordinates(angle_min, angle_max,point_count): - point_count_final=point_count - if angle_max < angle_min: - a = angle_min - angle_min = angle_max - angle_max = a - point_count=point_count*90/(angle_max-angle_min) - actual_points=0 - while actual_pointsangle_min and x20: - point_count=point_count+3 - else: - point_count=point_count+1 - return filtered - diff --git a/update/mini/fla.py b/update/mini/fla.py deleted file mode 100644 index 8c6ab3d..0000000 --- a/update/mini/fla.py +++ /dev/null @@ -1,100 +0,0 @@ -from flask import Flask, make_response, jsonify, request, abort -from PIL import Image -import gphoto2 as gp -from time import sleep -import shutil -from OpenScan import load_int, load_float, load_bool -import RPi.GPIO as GPIO - -GPIO.setwarnings(False) -GPIO.setmode(GPIO.BCM) - -app = Flask(__name__) - -basedir = '/home/pi/OpenScan/' - -################################################################################################################### -@app.route('/gphoto_init', methods=['get']) -def gphoto_init(): - global camera - camera = gp.Camera() - camera.init() - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_preview', methods=['get']) -def gphoto_preview(): - filepath = str(request.args.get('filepath')) - camera_file = gp.gp_camera_capture_preview(camera)[1] - target = basedir + filepath - camera_file.save(target) - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_capture', methods=['get']) -def gphoto_capture(): - filepath = str(request.args.get('filepath')) - file_path = camera.capture(gp.GP_CAPTURE_IMAGE) - camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) - camera_file.save(basedir + filepath) - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_test', methods=['get']) -def gphoto_test(): - text = camera.get_summary() - return ({}, 200) -################################################################################################################### -@app.route('/gphoto_exit', methods=['get']) -def gphoto_exit(): - global camera - camera.exit() - return ({}, 200) -################################################################################################################### -@app.route('/crop', methods=['get']) -def crop(): - downscale_threshold = 1000 - filepath_in = basedir + str(request.args.get('filepath_in')) - filepath_out = basedir + str(request.args.get('filepath_out')) - cropx = int(request.args.get('cropx'))/200 - cropy = int(request.args.get('cropy'))/200 - rotation = int(request.args.get('rotation')) - preview = str(request.args.get('preview')) - - with Image.open(filepath_in) as img: - w,h = img.size - if cropx != 0 or cropy != 0: - img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) - if rotation == 90: - img = img.transpose(Image.ROTATE_90) - elif rotation == 180: - img= img.transpose(Image.ROTATE_180) - elif rotation == 270: - img= img.transpose(Image.ROTATE_270) - if preview == "True": - w,h = img.size - if w > downscale_threshold or h > downscale_threshold: - downscale = max(w/downscale_threshold,h/downscale_threshold) - img = img.resize((int(w/downscale),int(h/downscale)),Image.ANTIALIAS) - img.save(filepath_out, quality=95, subsampling=0) - return ({}, 200) - -################################################################################################################### -@app.route('/external_capture', methods=['get']) -def external_capture(): - pin = load_int('pin_external') - delay_before = load_float('cam_delay_before') - timeout = load_float('cam_timeout')/1000 - delay_after = load_float('cam_delay_after') - GPIO.setup(pin, GPIO.OUT) - GPIO.output(pin, GPIO.LOW) - sleep(delay_before) - GPIO.output(pin, GPIO.HIGH) - sleep(timeout) - GPIO.output(pin, GPIO.LOW) - sleep(delay_after) - return ({}, 200) - - - - -if __name__ == '__main__': - app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) -# app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/mini/flows.json b/update/mini/flows.json deleted file mode 100644 index 83c64c0..0000000 --- a/update/mini/flows.json +++ /dev/null @@ -1,5500 +0,0 @@ -[ - { - "id": "829d803b6033a693", - "type": "tab", - "label": "HOME", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "1613373abaf77a2c", - "type": "tab", - "label": "SCAN", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "4981d84ef1a366d1", - "type": "tab", - "label": "Files&Cloud", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "017bd4e4a428bee5", - "type": "tab", - "label": "SETTINGS", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "b3150b13e34b1fe8", - "type": "ui_tab", - "name": "OpenScan", - "icon": "dashboard", - "order": 1, - "disabled": false, - "hidden": false - }, - { - "id": "b6e9c2df6b28ff66", - "type": "ui_base", - "theme": { - "name": "theme-dark", - "lightTheme": { - "default": "#0094CE", - "baseColor": "#0094CE", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "darkTheme": { - "default": "#097479", - "baseColor": "#097479", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "customTheme": { - "name": "Untitled Theme 1", - "default": "#4B7930", - "baseColor": "#4B7930", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" - }, - "themeState": { - "base-color": { - "default": "#097479", - "value": "#097479", - "edited": false - }, - "page-titlebar-backgroundColor": { - "value": "#097479", - "edited": false - }, - "page-backgroundColor": { - "value": "#111111", - "edited": false - }, - "page-sidebar-backgroundColor": { - "value": "#333333", - "edited": false - }, - "group-textColor": { - "value": "#0eb8c0", - "edited": false - }, - "group-borderColor": { - "value": "#555555", - "edited": false - }, - "group-backgroundColor": { - "value": "#333333", - "edited": false - }, - "widget-textColor": { - "value": "#eeeeee", - "edited": false - }, - "widget-backgroundColor": { - "value": "#097479", - "edited": false - }, - "widget-borderColor": { - "value": "#333333", - "edited": false - }, - "base-font": { - "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" - } - }, - "angularTheme": { - "primary": "indigo", - "accents": "blue", - "warn": "red", - "background": "grey", - "palette": "light" - } - }, - "site": { - "name": "OpenScan 3D Scanner", - "hideToolbar": "false", - "allowSwipe": "false", - "lockMenu": "false", - "allowTempTheme": "true", - "dateFormat": "DD/MM/YYYY", - "sizes": { - "sx": 46, - "sy": 46, - "gx": 10, - "gy": 10, - "cx": 6, - "cy": 6, - "px": 6, - "py": 6 - } - } - }, - { - "id": "729f9ea6e3513c9b", - "type": "ui_group", - "name": "Home", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "65ae49b64fa0d83e", - "type": "ui_tab", - "name": "Settings", - "icon": "dashboard", - "order": 4, - "disabled": false, - "hidden": false - }, - { - "id": "e23b837a9f040895", - "type": "ui_tab", - "name": "Scan", - "icon": "dashboard", - "order": 2, - "disabled": false, - "hidden": false - }, - { - "id": "7aaf184330605300", - "type": "ui_group", - "name": "Settings", - "tab": "e23b837a9f040895", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "90223f7ddc082321", - "type": "ui_group", - "name": "Arducam", - "tab": "e23b837a9f040895", - "order": 3, - "disp": false, - "width": 10, - "collapse": false, - "className": "" - }, - { - "id": "7625f9c9e8dbc5c6", - "type": "ui_spacer", - "z": "017bd4e4a428bee5", - "name": "spacer", - "group": "", - "order": 4, - "width": 1, - "height": 1 - }, - { - "id": "3b4bd36726be16d5", - "type": "ui_group", - "name": "Settings", - "tab": "65ae49b64fa0d83e", - "order": 3, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "b5fdd57b.15eda8", - "type": "ui_group", - "name": "Main", - "tab": "15a222ed.d70a7d", - "order": 1, - "disp": false, - "width": 13, - "collapse": false - }, - { - "id": "db43d646.2074c8", - "type": "ui_group", - "name": "OpenScanCloud", - "tab": "15a222ed.d70a7d", - "order": 2, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "15a222ed.d70a7d", - "type": "ui_tab", - "name": "Files&Cloud", - "icon": "dashboard", - "order": 3, - "disabled": false, - "hidden": false - }, - { - "id": "d9b28c5fbfd37509", - "type": "ui_spacer", - "z": "1613373abaf77a2c", - "name": "spacer", - "group": "7aaf184330605300", - "order": 21, - "width": 1, - "height": 1 - }, - { - "id": "cc7d61f1cfd44578", - "type": "ui_spacer", - "z": "1613373abaf77a2c", - "name": "spacer", - "group": "7aaf184330605300", - "order": 24, - "width": 1, - "height": 1 - }, - { - "id": "bba223eda03b67eb", - "type": "ui_spacer", - "z": "1613373abaf77a2c", - "name": "spacer", - "group": "7aaf184330605300", - "order": 25, - "width": 1, - "height": 1 - }, - { - "id": "e28012f571abd8dc", - "type": "ui_spacer", - "z": "1613373abaf77a2c", - "name": "spacer", - "group": "7aaf184330605300", - "order": 28, - "width": 1, - "height": 1 - }, - { - "id": "b32d71fea63612b7", - "type": "ui_spacer", - "z": "1613373abaf77a2c", - "name": "spacer", - "group": "90223f7ddc082321", - "order": 1, - "width": 2, - "height": 1 - }, - { - "id": "4cf8566a9121fe3f", - "type": "ui_spacer", - "z": "1613373abaf77a2c", - "name": "spacer", - "group": "90223f7ddc082321", - "order": 7, - "width": 2, - "height": 1 - }, - { - "id": "2cc829a5.610f36", - "type": "ui_group", - "name": "group-test", - "tab": "", - "order": 3, - "disp": false, - "width": 3, - "collapse": false - }, - { - "id": "65285822f24120f4", - "type": "ui_spacer", - "z": "017bd4e4a428bee5", - "name": "spacer", - "group": "3b4bd36726be16d5", - "order": 8, - "width": 6, - "height": 1 - }, - { - "id": "90d444dfdfccf138", - "type": "ui_spacer", - "z": "017bd4e4a428bee5", - "name": "spacer", - "group": "3b4bd36726be16d5", - "order": 15, - "width": 6, - "height": 1 - }, - { - "id": "c9dbf6219837d6d7", - "type": "ui_spacer", - "z": "017bd4e4a428bee5", - "name": "spacer", - "group": "3b4bd36726be16d5", - "order": 22, - "width": 6, - "height": 1 - }, - { - "id": "3fe52603e2ac73b6", - "type": "ui_template", - "z": "829d803b6033a693", - "group": "729f9ea6e3513c9b", - "name": "Background", - "order": 1, - "width": 0, - "height": 0, - "format": "", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": false, - "templateScope": "global", - "className": "", - "x": 110, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "4468f691.103eb8", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 2, - "width": 2, - "height": 2, - "passthru": false, - "label": "SCAN", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "1", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 100, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "6560dd25.9e76c4", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 4, - "width": 2, - "height": 2, - "passthru": false, - "label": "Settings", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "3", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 100, - "y": 180, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "62cd5288.2805fc", - "type": "ui_ui_control", - "z": "829d803b6033a693", - "name": "", - "events": "all", - "x": 300, - "y": 100, - "wires": [ - [] - ] - }, - { - "id": "71e72293.91c6fc", - "type": "ui_button", - "z": "829d803b6033a693", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 3, - "width": 2, - "height": 2, - "passthru": false, - "label": "Files", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "2", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 140, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "88edad7ca53698fd", - "type": "inject", - "z": "829d803b6033a693", - "name": "1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "1", - "topic": "", - "payload": "true", - "payloadType": "bool", - "x": 90, - "y": 320, - "wires": [ - [ - "000a811a215e08d4", - "83c2b5ea51f0fec3", - "88fde4ab78c965d7", - "bee62d2a99cbc63b", - "4fa53fb9738cb1d3" - ] - ] - }, - { - "id": "bd75f33b8a57c522", - "type": "link out", - "z": "829d803b6033a693", - "name": "enable", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "92c98e6ce7cd25f9" - ], - "x": 335, - "y": 360, - "wires": [] - }, - { - "id": "000a811a215e08d4", - "type": "function", - "z": "829d803b6033a693", - "name": "enable", - "func": "msg.enabled = true\nmsg.payload = 1\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 360, - "wires": [ - [ - "bd75f33b8a57c522" - ] - ] - }, - { - "id": "83c2b5ea51f0fec3", - "type": "function", - "z": "829d803b6033a693", - "name": "disable", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 240, - "y": 400, - "wires": [ - [ - "6b94bf2295b1b31d" - ] - ] - }, - { - "id": "6b94bf2295b1b31d", - "type": "link out", - "z": "829d803b6033a693", - "name": "disable", - "mode": "link", - "links": [ - "a1d29e56599da0bd" - ], - "x": 335, - "y": 400, - "wires": [] - }, - { - "id": "88fde4ab78c965d7", - "type": "function", - "z": "829d803b6033a693", - "name": "write", - "func": "var file = 'status_cloud'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\ncontent = 'ready'\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "960912e90ba5b5bc", - "type": "link out", - "z": "829d803b6033a693", - "name": "started1s", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "e0965e490d53617f", - "612a7556ab11cf7d", - "482bc06e02eec5b9" - ], - "x": 615, - "y": 720, - "wires": [] - }, - { - "id": "168d72a54504b327", - "type": "inject", - "z": "829d803b6033a693", - "name": "5/0.1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "0.1", - "crontab": "", - "once": true, - "onceDelay": "5", - "topic": "", - "payload": "", - "payloadType": "str", - "x": 100, - "y": 620, - "wires": [ - [ - "6c6ef2255a7d39e5" - ] - ] - }, - { - "id": "6c6ef2255a7d39e5", - "type": "link out", - "z": "829d803b6033a693", - "name": "repeat 5s/0.1s", - "mode": "link", - "links": [ - "61990987acd0f263", - "2415272f42ce468c" - ], - "x": 195, - "y": 640, - "wires": [] - }, - { - "id": "bee62d2a99cbc63b", - "type": "function", - "z": "829d803b6033a693", - "name": "global", - "func": "global.set('flag_pw', true)\nglobal.set('flag', true)\nglobal.set('combine', false)\nglobal.set('focus', 2838)\nglobal.set('focus1', 0)\nglobal.set('focus2', 0)\n\nglobal.set('focuser', true)\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 230, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "544d20f02215011a", - "type": "function", - "z": "829d803b6033a693", - "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cameras':{\n 'imx519':[4656,3496],\n 'imx219':[3280,2464],\n 'imx477':[4056,3040],\n 'ov5647':[2592,1944],\n 'imx378':[3840,2880],\n 'ov9271':[1280,800],\n 'imx290a':[1920,1080],\n 'imx290b':[1920,1080],\n },\n 'cam_AFmode':true,\n 'cam_STmode':true,\n 'cam_stacksize':2,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':0,\n 'cam_saturation':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'hostname':'',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n 'pin_endstop1':24,\n 'pin_endstop2':25,\n 'pin_external':10,\n 'pin_ringlight1':17,\n 'pin_ringlight2':27,\n 'pin_rotor_dir':5,\n 'pin_rotor_enable':23,\n 'pin_rotor_step':6,\n 'pin_tt_dir':9,\n 'pin_tt_enable':22,\n 'pin_tt_step':11,\n 'rotor_acc':1,\n 'rotor_accramp':2000,\n 'rotor_angle':10,\n 'rotor_anglemax':60,\n 'rotor_anglemin':-40,\n 'rotor_anglestart':0,\n 'rotor_delay':0.0001,\n 'rotor_dir':1,\n 'rotor_stepsperrotation':48000,\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n 'tt_acc':1,\n 'tt_accramp':200,\n 'tt_angle':10,\n 'tt_delay':0.0001,\n 'tt_dir':1,\n 'tt_stepsperrotation':3200,\n 'cam_focus':2838,\n 'cam_focus1':0,\n 'cam_focus2':0,\n 'uploadprogress':'',\n 'update_type':'main',\n 'update_auto':true,\n 'turntable_mode':true,\n}}\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 310, - "y": 720, - "wires": [ - [ - "c77552216a8bb781" - ] - ] - }, - { - "id": "a1f0ed7d5a9d670e", - "type": "inject", - "z": "829d803b6033a693", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "false", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "0.1", - "topic": "", - "x": 90, - "y": 720, - "wires": [ - [ - "544d20f02215011a" - ] - ] - }, - { - "id": "c77552216a8bb781", - "type": "python3-function", - "z": "829d803b6033a693", - "name": "chk files", - "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", - "outputs": 1, - "x": 520, - "y": 720, - "wires": [ - [ - "960912e90ba5b5bc" - ] - ] - }, - { - "id": "38783aea9cc317a6", - "type": "link in", - "z": "829d803b6033a693", - "name": "factory reset", - "links": [ - "80bccc884b0be297" - ], - "x": 135, - "y": 760, - "wires": [ - [ - "544d20f02215011a" - ] - ] - }, - { - "id": "4fa53fb9738cb1d3", - "type": "python3-function", - "z": "829d803b6033a693", - "name": "create log", - "func": "import subprocess\n\n\nlog = '############################################DMESG############################################\\n'\nlog += subprocess.getoutput(\"dmesg\")\nlog += '\\n############################################SYSLOG############################################\\n'\nlog += subprocess.getoutput(\"tail -10000 /var/log/syslog\")\n\nwith open('/home/pi/OpenScan/tmp/log.txt', 'w+') as file:\n file.write(log)\n\nreturn msg", - "outputs": 1, - "x": 240, - "y": 480, - "wires": [ - [] - ] - }, - { - "id": "828e5298.d2192", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 9, - "width": 2, - "height": 1, - "passthru": false, - "label": "⇐", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 400, - "wires": [ - [ - "b12e54fb.3141b8" - ] - ] - }, - { - "id": "96c7e241.458e6", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 10, - "width": 2, - "height": 1, - "passthru": false, - "label": "⇒", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 440, - "wires": [ - [ - "37f52dd4.bd7572" - ] - ] - }, - { - "id": "2e854876.6b6008", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 6, - "width": 2, - "height": 1, - "passthru": true, - "label": "⇑", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 280, - "wires": [ - [ - "555aea34.b3b5e4" - ] - ] - }, - { - "id": "753817f.1b9b3e8", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "", - "group": "7aaf184330605300", - "order": 7, - "width": 2, - "height": 1, - "passthru": true, - "label": "⇓", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 320, - "wires": [ - [ - "9905e0c9.dddcd" - ] - ] - }, - { - "id": "8775044.3aa3ef8", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 8, - "width": 2, - "height": 1, - "name": "", - "label": "Turntable", - "format": "", - "layout": "row-left", - "className": "", - "x": 100, - "y": 360, - "wires": [] - }, - { - "id": "9e8a2d23.bf6ce", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 5, - "width": 2, - "height": 1, - "name": "", - "label": "Rotor", - "format": "", - "layout": "row-left", - "className": "", - "x": 90, - "y": 240, - "wires": [] - }, - { - "id": "555aea34.b3b5e4", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 280, - "wires": [ - [ - "46e00b45.c24ca4" - ] - ] - }, - { - "id": "9905e0c9.dddcd", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 320, - "wires": [ - [ - "6ee089cb343a35ef" - ] - ] - }, - { - "id": "b12e54fb.3141b8", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 400, - "wires": [ - [ - "c1871a2b9af5419a" - ] - ] - }, - { - "id": "37f52dd4.bd7572", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "outputs": 1, - "x": 220, - "y": 440, - "wires": [ - [ - "42b9f1fc49e69f54" - ] - ] - }, - { - "id": "46e00b45.c24ca4", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Rotor left", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('rotor',load_int('rotor_angle'))", - "outputs": 1, - "x": 360, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "6ee089cb343a35ef", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Rotor right", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('rotor',-load_int('rotor_angle'))", - "outputs": 1, - "x": 370, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "42b9f1fc49e69f54", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "TT right", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('tt',-load_int('tt_angle'))", - "outputs": 1, - "x": 360, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "c1871a2b9af5419a", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "TT left", - "func": "from OpenScan import motorrun, load_int\n\nmotorrun('tt',load_int('tt_angle'))", - "outputs": 1, - "x": 350, - "y": 400, - "wires": [ - [] - ] - }, - { - "id": "aebad788761dce4a", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "routine_photocount", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 14, - "width": 3, - "height": 1, - "passthru": true, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "10", - "max": "300", - "step": "10", - "className": "", - "x": 350, - "y": 540, - "wires": [ - [ - "ce28a0b5bfb0d5a1" - ] - ] - }, - { - "id": "107a030938cbfea9", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 540, - "wires": [ - [ - "aebad788761dce4a" - ] - ] - }, - { - "id": "ce28a0b5bfb0d5a1", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 540, - "wires": [ - [] - ] - }, - { - "id": "84d6b96c8ebaac96", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadF", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 580, - "wires": [ - [ - "470b10726d298834" - ] - ] - }, - { - "id": "470b10726d298834", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "shutter ", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 16, - "width": 3, - "height": 1, - "passthru": true, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "300", - "step": "1", - "className": "", - "x": 310, - "y": 580, - "wires": [ - [ - "44c3947a9b92d32d" - ] - ] - }, - { - "id": "44c3947a9b92d32d", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 580, - "wires": [ - [] - ] - }, - { - "id": "069bcf58b1fe44cd", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 13, - "width": 3, - "height": 1, - "name": "photocount", - "label": "Photos", - "format": "", - "layout": "row-left", - "className": "", - "x": 670, - "y": 540, - "wires": [] - }, - { - "id": "8dc7df1de59cb03a", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 15, - "width": 3, - "height": 1, - "name": "shutter", - "label": "Shutter (ms)", - "format": "", - "layout": "row-left", - "className": "", - "x": 650, - "y": 580, - "wires": [] - }, - { - "id": "cc69dba8d54a29dd", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "Crop X", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 18, - "width": 3, - "height": 1, - "passthru": true, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "99", - "step": "1", - "className": "", - "x": 320, - "y": 620, - "wires": [ - [ - "c2b2ab5524271123" - ] - ] - }, - { - "id": "e3a90602605fb9e9", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "Crop Y", - "label": " ", - "tooltip": "", - "group": "7aaf184330605300", - "order": 20, - "width": 3, - "height": 1, - "passthru": true, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "99", - "step": "1", - "className": "", - "x": 310, - "y": 660, - "wires": [ - [ - "26f17a7f406df73c" - ] - ] - }, - { - "id": "9c6b48b7b4cc4e1a", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 620, - "wires": [ - [ - "cc69dba8d54a29dd" - ] - ] - }, - { - "id": "c470fd0b15356206", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 660, - "wires": [ - [ - "e3a90602605fb9e9" - ] - ] - }, - { - "id": "c2b2ab5524271123", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 620, - "wires": [ - [] - ] - }, - { - "id": "26f17a7f406df73c", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 660, - "wires": [ - [] - ] - }, - { - "id": "fecf5cff888bb570", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 17, - "width": 3, - "height": 1, - "name": "cropx", - "label": "Crop X (%)", - "format": "", - "layout": "row-left", - "className": "", - "x": 650, - "y": 620, - "wires": [] - }, - { - "id": "0ee4950bd21498bd", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 19, - "width": 3, - "height": 1, - "name": "cropy", - "label": "Crop Y (%)", - "format": "", - "layout": "row-left", - "className": "", - "x": 650, - "y": 660, - "wires": [] - }, - { - "id": "ebbf11b55d758806", - "type": "ui_text_input", - "z": "1613373abaf77a2c", - "name": "", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 4, - "width": 3, - "height": 1, - "passthru": true, - "mode": "text", - "delay": "0", - "topic": "", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 320, - "y": 500, - "wires": [ - [ - "67385b196c517ac6" - ] - ] - }, - { - "id": "f4b3112a9ec6c487", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.payload=\"default\"\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 500, - "wires": [ - [ - "ebbf11b55d758806" - ] - ] - }, - { - "id": "67385b196c517ac6", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 500, - "wires": [ - [] - ] - }, - { - "id": "4dd7285c2b0fd79b", - "type": "ui_slider", - "z": "1613373abaf77a2c", - "name": "ringlight", - "label": "", - "tooltip": "", - "group": "7aaf184330605300", - "order": 12, - "width": 3, - "height": 1, - "passthru": true, - "outs": "all", - "topic": "", - "topicType": "str", - "min": 0, - "max": "3", - "step": 1, - "className": "", - "x": 320, - "y": 700, - "wires": [ - [ - "873dace18a23fdf2" - ] - ] - }, - { - "id": "873dace18a23fdf2", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "LED", - "func": "from OpenScan import ringlight\nval = msg['payload']\n\nif val == 0:\n ringlight(1,False)\n ringlight(2,False)\nelif val == 1:\n ringlight(1,True)\n ringlight(2,False)\nelif val == 2:\n ringlight(1,False)\n ringlight(2,True)\nelif val == 3:\n ringlight(1,True)\n ringlight(2,True)\n", - "outputs": 1, - "x": 510, - "y": 700, - "wires": [ - [] - ] - }, - { - "id": "9e30e33a1520fee0", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadI", - "func": "msg.payload = 0\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 700, - "wires": [ - [ - "4dd7285c2b0fd79b" - ] - ] - }, - { - "id": "7dd287f40385922f", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "start ", - "group": "7aaf184330605300", - "order": 22, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-play", - "payload": "", - "payloadType": "date", - "topic": "enabled", - "topicType": "str", - "x": 150, - "y": 880, - "wires": [ - [ - "431f917c2541ae48", - "33d94a04b96a2de0", - "6d15f717d5a11002" - ] - ] - }, - { - "id": "579f2211199fd6ab", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "stop", - "group": "7aaf184330605300", - "order": 23, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-stop", - "payload": "numberofphotos", - "payloadType": "global", - "topic": "", - "topicType": "str", - "x": 750, - "y": 920, - "wires": [ - [ - "1787f08ed7070ddd", - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "431f917c2541ae48", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Routine", - "func": "from OpenScan import load_str, load_int, motorrun, create_coordinates, take_photo, save, load_bool, camera\nfrom time import sleep, strftime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system\nfrom os.path import isfile\nfrom Arducam import Focuser\n\nstatus = load_str(\"status_internal_cam\")\n\nif status == \"no camera found\":\n return\n\nif not status == \"Routine-stopping\":\n save('status_internal_cam','Routine-preparing')\n\nprojectname=load_str(\"routine_projectname\")\nphotocount = load_int('routine_photocount') #vorher point_count\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nangle_start = load_int('rotor_anglestart')\n\ncounter = 0\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp/tmp.jpg'\nzippath = basepath + 'tmp/tmp.zip'\n\nif not 'projectcode' in msg:\n projectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n msg['projectcode'] = projectcode\n msg['counter'] = -1\n if isfile(zippath):\n system('rm ' + zippath)\n sleep(1)\n\nprojectcode = msg['projectcode']\nmsg['counter'] += 1\n\ntt_mode = load_bool('turntable_mode')\n\nif tt_mode == False:\n coordinates = create_coordinates(angle_min,angle_max,photocount)\nelse:\n angle_start = 0\n coordinates = []\n for i in range (photocount):\n coordinates.append([0,360/photocount*(i+1)])\n\nposition_last = (angle_start , 0)\n\nzip = ZipFile(zippath, \"a\",ZIP_DEFLATED, allowZip64=True)\n\nfor position in coordinates:\n counter += 1\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n\n\n rotor_angle = position_last[0] - position[0]\n if abs(rotor_angle) > 180:\n rotor_angle = -360 * rotor_angle/abs(rotor_angle) + rotor_angle\n\n tt_angle = position_last[1] - position[1]\n #if abs(tt_angle) > 180:\n # tt_angle = -360 * tt_angle/abs(tt_angle) + tt_angle\n \n motorrun('rotor', rotor_angle)\n motorrun('tt', tt_angle)\n\n\n\n msg['cropx'] = load_int('cam_cropx')\n msg['cropy'] = load_int('cam_cropy')\n msg['rotation'] = load_int('cam_rotation')\n msg['filepath_in'] = 'tmp/tmp.jpg'\n msg['filepath_out'] = 'tmp/tmp.jpg'\n msg['filepath'] = 'tmp/tmp.jpg'\n if load_str('status_internal_cam') != \"Routine-stopping\":\n save('status_internal_cam','Routine-Photo ' + str(counter) + '/' + str(photocount))\n take_photo('tmp/tmp.jpg')\n camera('/crop',msg)\n\n\n \n zip.write(temppath, projectname + '_' + str(counter + msg['counter']*photocount) + \".jpg\")\n system('cp ' + temppath + ' ' + basepath +'tmp/preview.jpg')\n\n position_last = position\n\n\nzip.close()\n\n\nsave('status_internal_cam','Routine-done')\n\nmotorrun('rotor',position_last[0] - angle_start)\nmotorrun('tt',position_last[1])\n\nsave('status_internal_cam','--READY--')\n\nmsg['topic'] = 'Scan done'\nmsg['payload'] = 'Do you want to continue scanning or finish this project?'\nmsg['enabled'] = False\nreturn msg\n\n", - "outputs": 1, - "x": 300, - "y": 840, - "wires": [ - [ - "db7eea74d3bf892b" - ] - ] - }, - { - "id": "1787f08ed7070ddd", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "stop", - "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", - "outputs": 1, - "x": 930, - "y": 920, - "wires": [ - [] - ] - }, - { - "id": "e9b13dfd9f8d3711", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6" - ], - "x": 395, - "y": 800, - "wires": [] - }, - { - "id": "5ba05110851a5096", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "reboot", - "group": "7aaf184330605300", - "order": 26, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "reboot", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-repeat", - "payload": "", - "payloadType": "date", - "topic": "", - "topicType": "str", - "x": 90, - "y": 1000, - "wires": [ - [ - "16c76929f88df841" - ] - ] - }, - { - "id": "152d402caa595189", - "type": "ui_button", - "z": "1613373abaf77a2c", - "name": "shutdown", - "group": "7aaf184330605300", - "order": 27, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "shutdown", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-power-off", - "payload": "", - "payloadType": "date", - "topic": "", - "topicType": "str", - "x": 100, - "y": 1040, - "wires": [ - [ - "597bfb653e8cddbf" - ] - ] - }, - { - "id": "16c76929f88df841", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "9bb0adbd716ce347" - ], - "x": 215, - "y": 1000, - "wires": [] - }, - { - "id": "597bfb653e8cddbf", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "fc9abb94c35eec56" - ], - "x": 215, - "y": 1040, - "wires": [] - }, - { - "id": "9654deebb668e012", - "type": "inject", - "z": "1613373abaf77a2c", - "name": "1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "1", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 110, - "y": 960, - "wires": [ - [ - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "8367cfa0bf5bc5df", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start routine", - "links": [ - "210ef5246d1a8790", - "84608db962fd9932", - "8689e938.dd9e38", - "f20f2dbc.0f123", - "e9b13dfd9f8d3711", - "96bdb9417e38810f", - "fb13752beddee9f2", - "bd75f33b8a57c522" - ], - "x": 55, - "y": 880, - "wires": [ - [ - "7dd287f40385922f" - ] - ] - }, - { - "id": "fb13752beddee9f2", - "type": "link out", - "z": "1613373abaf77a2c", - "name": "", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "482bc06e02eec5b9" - ], - "x": 865, - "y": 880, - "wires": [] - }, - { - "id": "95439678bb2df2a2", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "msg.flag = global.get('flag')\nif (global.get('flag_pw')== true){\n return msg\n}\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 1160, - "wires": [ - [ - "04cc2467807d2d6b", - "14f9617b5b301318" - ] - ] - }, - { - "id": "948a3ae4444685f2", - "type": "change", - "z": "1613373abaf77a2c", - "name": "flag_pw true", - "rules": [ - { - "t": "set", - "p": "flag_pw", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 650, - "y": 1200, - "wires": [ - [] - ] - }, - { - "id": "04cc2467807d2d6b", - "type": "change", - "z": "1613373abaf77a2c", - "name": "flag_pw false", - "rules": [ - { - "t": "set", - "p": "flag_pw", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 430, - "y": 1200, - "wires": [ - [] - ] - }, - { - "id": "12f1399b240830bf", - "type": "exec", - "z": "1613373abaf77a2c", - "command": " v4l2-ctl --list-formats-ext", - "addpay": "", - "append": "", - "useSpawn": "true", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "check cam", - "x": 190, - "y": 100, - "wires": [ - [ - "6222f781629c72e7" - ], - [ - "6222f781629c72e7" - ], - [] - ] - }, - { - "id": "6222f781629c72e7", - "type": "function", - "z": "1613373abaf77a2c", - "name": "write", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\ncontent = '--READY--'\n\nif (msg.payload.includes('Cannot open device')){\n content = 'no camera found'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return msg\n }\n });\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 350, - "y": 100, - "wires": [ - [] - ] - }, - { - "id": "e978bf8c53d1f15a", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "Settings internal cam", - "info": "", - "x": 120, - "y": 40, - "wires": [] - }, - { - "id": "e9566588c5e40637", - "type": "inject", - "z": "1613373abaf77a2c", - "name": "4s/0.5", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "0.5", - "crontab": "", - "once": true, - "onceDelay": "10", - "topic": "Repeat", - "payload": "0.2", - "payloadType": "str", - "x": 120, - "y": 1160, - "wires": [ - [ - "95439678bb2df2a2" - ] - ] - }, - { - "id": "14f9617b5b301318", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\nif msg['flag'] == False and not 'Routine' in status:\n return\n\nmsg['payload']=\"/tmp/preview.jpg?ts=\"+str(int(time()*10))\n\nif status!=\"--READY--\" and status!=\"ERROR:flask\":\n return msg\n\nmsg['cropx'] = load_int('cam_cropx')\nmsg['cropy'] = load_int('cam_cropy')\nmsg['rotation'] = load_int('cam_rotation')\nmsg['filepath_in'] = 'tmp/tmp.jpg'\nmsg['filepath_out'] = 'tmp/preview.jpg'\nmsg['preview'] = True\n\ntake_photo('tmp/tmp.jpg')\n\ntry:\n camera('/crop',msg)\nexcept:\n save('status_internal_cam','ERROR: flask')\n pass\n \n\nreturn msg\n", - "outputs": 1, - "x": 450, - "y": 1160, - "wires": [ - [ - "948a3ae4444685f2", - "8f5d87ce24c40b11" - ] - ] - }, - { - "id": "1118d0965ff7c40b", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 3, - "width": 3, - "height": 1, - "name": "projectname", - "label": "Projectname", - "format": "", - "layout": "row-left", - "className": "", - "x": 670, - "y": 500, - "wires": [] - }, - { - "id": "82c8ad50ecfbc755", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 11, - "width": 3, - "height": 1, - "name": "ringlight", - "label": "Ringlight", - "format": "", - "layout": "row-left", - "className": "", - "x": 660, - "y": 700, - "wires": [] - }, - { - "id": "33d94a04b96a2de0", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 920, - "wires": [ - [ - "579f2211199fd6ab" - ] - ] - }, - { - "id": "c1c044f3c2139f68", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 750, - "y": 960, - "wires": [ - [ - "579f2211199fd6ab" - ] - ] - }, - { - "id": "9a368472a72fbc48", - "type": "comment", - "z": "1613373abaf77a2c", - "name": "preview arducam with focus", - "info": "", - "x": 160, - "y": 1100, - "wires": [] - }, - { - "id": "8f5d87ce24c40b11", - "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "preview_arducam", - "order": 8, - "width": 10, - "height": 12, - "format": "
\n\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 670, - "y": 1160, - "wires": [ - [] - ] - }, - { - "id": "282efe64332193c8", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "focus", - "func": "from OpenScan import load_str\n\nif load_str('camera') != 'imx519':\n return\n\nfrom Arducam import Focuser\n\n\nif msg['focuser'] == True:\n focuser = Focuser('/dev/v4l-subdev1')\n focuser.write(msg['focus'])\n return msg", - "outputs": 1, - "x": 1110, - "y": 1320, - "wires": [ - [] - ] - }, - { - "id": "64b16ef47ab6d859", - "type": "ui_switch", - "z": "1613373abaf77a2c", - "name": "MF", - "label": "", - "tooltip": "", - "group": "90223f7ddc082321", - "order": 3, - "width": 1, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "false", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "true", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 150, - "y": 1260, - "wires": [ - [ - "f017f67a8d4a3750" - ] - ] - }, - { - "id": "f017f67a8d4a3750", - "type": "function", - "z": "1613373abaf77a2c", - "name": "enable", - "func": "let fs = global.get('fs');\nfilepath = '/home/pi/OpenScan/settings/';\n\nvar file = 'status_internal_cam'\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data != '--READY--'){\n return\n}\n\nfile = 'cam_AFmode'\ncontent = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});\n\nglobal.set('AF',msg.payload)\nmsg.enabled = false\nif (msg.payload == false){\n msg.enabled = true\n}\nif (msg.payload == true){\n file = 'cam_focus1'\n content = String(0)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n file = 'cam_focus2'\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n \n file = 'cam_stacksize'\n content = String(2)\n fs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });\n global.set('focus1', 0)\n global.set('focus2', 0)\n\n}\n\n\nmsg.focus = global.get('focus')\nmsg.payload = 'down'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 1260, - "wires": [ - [ - "5c39bd09.702d84", - "74521cf72050b515", - "b70e8c24ee011258" - ] - ] - }, - { - "id": "65145c939b6647e2", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "", - "links": [ - "960912e90ba5b5bc" - ], - "x": 55, - "y": 1260, - "wires": [ - [ - "64b16ef47ab6d859" - ] - ] - }, - { - "id": "5ea18678.975138", - "type": "trigger", - "z": "1613373abaf77a2c", - "name": "20ms", - "op1": "", - "op2": "0", - "op1type": "pay", - "op2type": "str", - "duration": "-20", - "extend": false, - "overrideDelay": false, - "units": "ms", - "reset": "", - "bytopic": "all", - "topic": "topic", - "outputs": 1, - "x": 730, - "y": 1300, - "wires": [ - [ - "fd93843e238cc9ce" - ] - ] - }, - { - "id": "5c39bd09.702d84", - "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "F+", - "order": 4, - "width": 1, - "height": 1, - "format": " ", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 410, - "y": 1260, - "wires": [ - [ - "dcfb5cce.0431a" - ] - ] - }, - { - "id": "dcfb5cce.0431a", - "type": "switch", - "z": "1613373abaf77a2c", - "name": "", - "property": "payload", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "1", - "vt": "num" - }, - { - "t": "eq", - "v": "-1", - "vt": "num" - }, - { - "t": "eq", - "v": "up", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 3, - "x": 550, - "y": 1280, - "wires": [ - [ - "5ea18678.975138", - "f4a41b1e7b221486" - ], - [ - "5ea18678.975138", - "f4a41b1e7b221486" - ], - [ - "8cdd0a6b.40bcd8" - ] - ] - }, - { - "id": "8cdd0a6b.40bcd8", - "type": "change", - "z": "1613373abaf77a2c", - "name": "", - "rules": [ - { - "t": "set", - "p": "reset", - "pt": "msg", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 560, - "y": 1340, - "wires": [ - [ - "5ea18678.975138", - "e9b3837b1ffb0360" - ] - ] - }, - { - "id": "74521cf72050b515", - "type": "ui_template", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "name": "F-", - "order": 5, - "width": 1, - "height": 1, - "format": " ", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 410, - "y": 1300, - "wires": [ - [ - "dcfb5cce.0431a" - ] - ] - }, - { - "id": "7219f62c9fdc6753", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 6, - "width": 2, - "height": 1, - "name": "", - "label": "{{msg.payload}}", - "format": "", - "layout": "col-center", - "className": "", - "x": 1130, - "y": 1280, - "wires": [] - }, - { - "id": "b70e8c24ee011258", - "type": "function", - "z": "1613373abaf77a2c", - "name": "global", - "func": "if (msg.payload == 'down'){\n msg.enabled = false\n msg.payload = ' '\n msg.focuser = global.get('focuser')\n return msg\n}\n\n\nmsg.enabled = true\n\nsign = msg.payload\nfocus = global.get('focus')\nif (focus > 3000){\n focusstep = 5\n}\nelse if (focus <=3000 && focus > 2000){\n focusstep = 3\n}\nelse{\n focusstep = 2\n}\n\n\nfocus = focus + sign * focusstep\n\nsign = msg.payload\nif (focus > 4000){\n distance = 6\n focus = 4000\n}\nelse if (focus > 1200 && focus <= 4000){\n distance = 737086 * Math.pow(focus, -1.4096)\n}\nelse if (focus <= 1200){\n distance = 999\n if (focus <=0){\n focus = 0\n }\n}\n\n\nglobal.set('focus', focus)\nmsg.focus = focus\nmsg.distance = distance\ndistance = distance * 10\nmsg.focuser = global.get('focuser')\nmsg.payload = String(distance.toFixed(1)) + 'mm'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 970, - "y": 1300, - "wires": [ - [ - "7219f62c9fdc6753", - "282efe64332193c8", - "704a9f89089d1f25" - ] - ] - }, - { - "id": "f4a41b1e7b221486", - "type": "change", - "z": "1613373abaf77a2c", - "name": "focuser f", - "rules": [ - { - "t": "set", - "p": "focuser", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 740, - "y": 1260, - "wires": [ - [] - ] - }, - { - "id": "e9b3837b1ffb0360", - "type": "change", - "z": "1613373abaf77a2c", - "name": "focuser t", - "rules": [ - { - "t": "set", - "p": "focuser", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 740, - "y": 1340, - "wires": [ - [] - ] - }, - { - "id": "fd93843e238cc9ce", - "type": "delay", - "z": "1613373abaf77a2c", - "name": "10ms", - "pauseType": "delay", - "timeout": "20", - "timeoutUnits": "milliseconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 850, - "y": 1300, - "wires": [ - [ - "b70e8c24ee011258" - ] - ] - }, - { - "id": "dfbfe28bac5c4221", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "90223f7ddc082321", - "order": 2, - "width": 1, - "height": 1, - "name": "MF", - "label": "MF", - "format": "", - "layout": "col-center", - "className": "", - "x": 150, - "y": 1300, - "wires": [] - }, - { - "id": "704a9f89089d1f25", - "type": "function", - "z": "1613373abaf77a2c", - "name": "save", - "func": "var file = 'cam_focus'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.focus)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1110, - "y": 1360, - "wires": [ - [] - ] - }, - { - "id": "917a194be245384a", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable projectname", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 540, - "wires": [ - [ - "f4b3112a9ec6c487" - ] - ] - }, - { - "id": "65cef204b16f8741", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable shutter", - "links": [ - "2d76e5617f13cd6c", - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 580, - "wires": [ - [ - "84d6b96c8ebaac96" - ] - ] - }, - { - "id": "2aea1727dbea76ce", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable cropx", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 620, - "wires": [ - [ - "9c6b48b7b4cc4e1a" - ] - ] - }, - { - "id": "4f212b44aa487945", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "enable cropy", - "links": [ - "a0ba1aa77c5c8b7c", - "a42c12e94f65fa01" - ], - "x": 55, - "y": 660, - "wires": [ - [ - "c470fd0b15356206" - ] - ] - }, - { - "id": "6d1e12f51f9af0b6", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start camchk", - "links": [ - "960912e90ba5b5bc" - ], - "x": 55, - "y": 100, - "wires": [ - [ - "12f1399b240830bf" - ] - ] - }, - { - "id": "8ebd1dcb5db156ed", - "type": "ui_text", - "z": "1613373abaf77a2c", - "group": "7aaf184330605300", - "order": 2, - "width": 6, - "height": 1, - "name": "", - "label": "Current Status:", - "format": " {{msg.payload}} ", - "layout": "row-spread", - "className": "", - "x": 320, - "y": 160, - "wires": [] - }, - { - "id": "94a7aec739f9266b", - "type": "function", - "z": "1613373abaf77a2c", - "name": "loadS", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 170, - "y": 160, - "wires": [ - [ - "8ebd1dcb5db156ed" - ] - ] - }, - { - "id": "2415272f42ce468c", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start status", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 55, - "y": 160, - "wires": [ - [ - "94a7aec739f9266b" - ] - ] - }, - { - "id": "a1e14624058e74cd", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "start routine settings", - "links": [ - "960912e90ba5b5bc" - ], - "x": 55, - "y": 500, - "wires": [ - [ - "f4b3112a9ec6c487", - "107a030938cbfea9", - "84d6b96c8ebaac96", - "9c6b48b7b4cc4e1a", - "c470fd0b15356206", - "9e30e33a1520fee0" - ] - ] - }, - { - "id": "1daf9e3a5bd5ab48", - "type": "function", - "z": "1613373abaf77a2c", - "name": "msg", - "func": "global.set('flag_pw', true)\nglobal.set('flag', true)\nmsg.enabled = true\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 750, - "y": 880, - "wires": [ - [ - "fb13752beddee9f2" - ] - ] - }, - { - "id": "6d15f717d5a11002", - "type": "function", - "z": "1613373abaf77a2c", - "name": "disable", - "func": "msg.enabled = false\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 300, - "y": 800, - "wires": [ - [ - "e9b13dfd9f8d3711" - ] - ] - }, - { - "id": "db7eea74d3bf892b", - "type": "ui_toast", - "z": "1613373abaf77a2c", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Finish", - "cancel": "Continue", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 450, - "y": 840, - "wires": [ - [ - "0b8661103366f834" - ] - ] - }, - { - "id": "0b8661103366f834", - "type": "python3-function", - "z": "1613373abaf77a2c", - "name": "continue", - "func": "from os import system\nfrom os.path import isfile\n\nif msg['payload'] == 'Continue':\n msg['enabled'] = True\n return msg,None\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp/tmp.jpg'\nzippath = basepath + 'tmp/tmp.zip'\nprojectcode = msg['projectcode']\n\nsystem('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')\n\nif isfile(zippath):\n system('rm ' + zippath)\n\nreturn None, msg", - "outputs": 2, - "x": 600, - "y": 880, - "wires": [ - [ - "431f917c2541ae48", - "579f2211199fd6ab" - ], - [ - "1daf9e3a5bd5ab48", - "579f2211199fd6ab" - ] - ] - }, - { - "id": "482bc06e02eec5b9", - "type": "link in", - "z": "1613373abaf77a2c", - "name": "preview", - "links": [ - "960912e90ba5b5bc", - "fb13752beddee9f2" - ], - "x": 55, - "y": 1200, - "wires": [ - [ - "95439678bb2df2a2" - ] - ] - }, - { - "id": "ea54fcc2.cfcc2", - "type": "python3-function", - "z": "4981d84ef1a366d1", - "name": "get dirs", - "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n\n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", - "outputs": 1, - "x": 480, - "y": 180, - "wires": [ - [ - "b9a3a0f9.bcbea" - ] - ] - }, - { - "id": "2f4c0f98.dee2", - "type": "link in", - "z": "4981d84ef1a366d1", - "name": "filelist", - "links": [ - "960912e90ba5b5bc", - "a4f09e25.02569", - "ed35109311335099", - "fb13752beddee9f2" - ], - "x": 355, - "y": 140, - "wires": [ - [ - "ea54fcc2.cfcc2" - ] - ] - }, - { - "id": "b9a3a0f9.bcbea", - "type": "ui_table", - "z": "4981d84ef1a366d1", - "group": "b5fdd57b.15eda8", - "name": "", - "order": 1, - "width": 13, - "height": 7, - "columns": [ - { - "field": "Date", - "title": "Date", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Name", - "title": "Name", - "width": "210", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Photos", - "title": "Photos", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Size", - "title": "Size", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Status", - "title": "Status", - "width": "140", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - } - ], - "outputs": 1, - "cts": true, - "x": 610, - "y": 180, - "wires": [ - [ - "50710948.71c308", - "4082b136.dae18", - "834046a4.647938", - "0c387c0291d6c131" - ] - ] - }, - { - "id": "952ce286.4ffd4", - "type": "ui_text", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "order": 3, - "width": 6, - "height": 1, - "name": "Status", - "label": "Status", - "format": "{{msg.status}}", - "layout": "row-spread", - "className": "", - "x": 250, - "y": 60, - "wires": [] - }, - { - "id": "d4383424.7807c8", - "type": "python3-function", - "z": "4981d84ef1a366d1", - "name": "upload", - "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", - "outputs": 2, - "x": 530, - "y": 420, - "wires": [ - [ - "9a132ab1.b21658" - ], - [ - "3d16b3789632784d", - "9a132ab1.b21658" - ] - ] - }, - { - "id": "50710948.71c308", - "type": "change", - "z": "4981d84ef1a366d1", - "name": "set", - "rules": [ - { - "t": "set", - "p": "set", - "pt": "global", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 750, - "y": 180, - "wires": [ - [ - "ada1b6f7cccc9344" - ] - ] - }, - { - "id": "834046a4.647938", - "type": "ui_text", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "order": 4, - "width": 6, - "height": 1, - "name": "Set", - "label": "Set:", - "format": "{{msg.payload.Name}}", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 220, - "wires": [] - }, - { - "id": "9a132ab1.b21658", - "type": "change", - "z": "4981d84ef1a366d1", - "name": "flag.true", - "rules": [ - { - "t": "set", - "p": "flag", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 700, - "y": 420, - "wires": [ - [ - "8689e938.dd9e38" - ] - ] - }, - { - "id": "3c67e97b.9d19a6", - "type": "function", - "z": "4981d84ef1a366d1", - "name": "enable", - "func": "if (global.get('flag') === false){\n msg.enabled = false\n msg.color=\"white\"\n}\nelse{\n msg.enabled = true\n msg.color=\"red\"\n \n}\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 130, - "y": 340, - "wires": [ - [ - "7a93d1e18254685c", - "e434ef42bd6b92e8", - "d5d840183025d91b", - "ab9e90ab5a53a0dd", - "478994f671a3907d" - ] - ] - }, - { - "id": "bfc01f26.c32cf", - "type": "change", - "z": "4981d84ef1a366d1", - "name": "flag.false", - "rules": [ - { - "t": "set", - "p": "flag", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 420, - "y": 460, - "wires": [ - [ - "f20f2dbc.0f123" - ] - ] - }, - { - "id": "b33d604c.5f1a6", - "type": "link in", - "z": "4981d84ef1a366d1", - "name": "enable cloud", - "links": [ - "4082b136.dae18", - "8689e938.dd9e38", - "e9b13dfd9f8d3711", - "f20f2dbc.0f123", - "fb13752beddee9f2" - ], - "x": 35, - "y": 340, - "wires": [ - [ - "3c67e97b.9d19a6" - ] - ] - }, - { - "id": "f6bd1a04.470838", - "type": "change", - "z": "4981d84ef1a366d1", - "name": "set", - "rules": [ - { - "t": "set", - "p": "payload", - "pt": "msg", - "to": "set", - "tot": "global" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 410, - "y": 420, - "wires": [ - [ - "d4383424.7807c8" - ] - ] - }, - { - "id": "4082b136.dae18", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "", - "links": [ - "b33d604c.5f1a6", - "87574a42938afec4" - ], - "x": 715, - "y": 140, - "wires": [] - }, - { - "id": "f20f2dbc.0f123", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "149e2e46b9623a2d" - ], - "x": 515, - "y": 460, - "wires": [] - }, - { - "id": "8689e938.dd9e38", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "149e2e46b9623a2d" - ], - "x": 795, - "y": 420, - "wires": [] - }, - { - "id": "15de0ebb.616d61", - "type": "ui_toast", - "z": "4981d84ef1a366d1", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 380, - "wires": [ - [ - "a7d89487.ee8858" - ] - ] - }, - { - "id": "a7d89487.ee8858", - "type": "python3-function", - "z": "4981d84ef1a366d1", - "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", - "outputs": 1, - "x": 690, - "y": 380, - "wires": [ - [ - "a4f09e25.02569" - ] - ] - }, - { - "id": "a4f09e25.02569", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "", - "links": [ - "2f4c0f98.dee2", - "c20357dd.374108", - "e9aab326.a6896", - "edd22cc7.befe1", - "19b81967.49db87", - "8ee1b3bb.7b0b3", - "d5246b3cc796afc6" - ], - "x": 775, - "y": 360, - "wires": [] - }, - { - "id": "7a93d1e18254685c", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "", - "mode": "link", - "links": [ - "809c9427e14e2448", - "92c98e6ce7cd25f9" - ], - "x": 235, - "y": 460, - "wires": [] - }, - { - "id": "4d99c601c9881680", - "type": "python3-function", - "z": "4981d84ef1a366d1", - "name": "refresh", - "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('sudo rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", - "outputs": 2, - "x": 320, - "y": 180, - "wires": [ - [ - "ea54fcc2.cfcc2", - "b42e061fb1f1f3d7" - ], - [ - "6434e713f088012b" - ] - ] - }, - { - "id": "372e95797a3f2f3b", - "type": "python3-function", - "z": "4981d84ef1a366d1", - "name": "limit :)", - "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", - "outputs": 1, - "x": 90, - "y": 220, - "wires": [ - [ - "573edbfdb7500ddc" - ] - ] - }, - { - "id": "573edbfdb7500ddc", - "type": "delay", - "z": "4981d84ef1a366d1", - "name": "", - "pauseType": "rate", - "timeout": "5", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 230, - "y": 220, - "wires": [ - [ - "c46e10b9c201913e" - ] - ] - }, - { - "id": "dacb1f078b624e10", - "type": "ui_toast", - "z": "4981d84ef1a366d1", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 340, - "wires": [ - [ - "c8d65cc7c2ff7c36" - ] - ] - }, - { - "id": "92c98e6ce7cd25f9", - "type": "link in", - "z": "4981d84ef1a366d1", - "name": "", - "links": [ - "7a93d1e18254685c", - "bd75f33b8a57c522" - ], - "x": 35, - "y": 180, - "wires": [ - [ - "c46e10b9c201913e" - ] - ] - }, - { - "id": "3d16b3789632784d", - "type": "ui_toast", - "z": "4981d84ef1a366d1", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "Terms", - "x": 690, - "y": 460, - "wires": [ - [] - ] - }, - { - "id": "6434e713f088012b", - "type": "ui_toast", - "z": "4981d84ef1a366d1", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "Terms", - "x": 470, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "c8d65cc7c2ff7c36", - "type": "python3-function", - "z": "4981d84ef1a366d1", - "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if os.path.isdir(dir + i):\n shutil.rmtree(dir + i)\n else:\n os.remove(dir + i)\n\nreturn msg", - "outputs": 1, - "x": 690, - "y": 340, - "wires": [ - [ - "a4f09e25.02569" - ] - ] - }, - { - "id": "6d471a5210505276", - "type": "function", - "z": "4981d84ef1a366d1", - "name": "read", - "func": "var file = 'status_cloud'\nvar file2 = 'status_uploadprogress'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nstatus = String(data);\n\nif (status.substr(0,9) === 'uploading'){\n data = fs.readFileSync(filepath+file2, 'utf8');\n progress = data.substr(data.length - 6)\n if (progress.substr(progress.length -1) === '%'){\n status = status + ' (' + progress + ')'\n }\n}\nmsg.status = status\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 130, - "y": 60, - "wires": [ - [ - "952ce286.4ffd4" - ] - ] - }, - { - "id": "f4e9a4bd79b4221f", - "type": "function", - "z": "4981d84ef1a366d1", - "name": "msg", - "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 340, - "wires": [ - [ - "dacb1f078b624e10" - ] - ] - }, - { - "id": "2806bf08ea21216d", - "type": "function", - "z": "4981d84ef1a366d1", - "name": "msg", - "func": "msg.Set=global.get('set').Set\nmsg.payload = 'Are you sure to delete the set and ALL associated files: ' + msg.Set + '?'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 380, - "wires": [ - [ - "15de0ebb.616d61" - ] - ] - }, - { - "id": "61990987acd0f263", - "type": "link in", - "z": "4981d84ef1a366d1", - "name": "", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 35, - "y": 60, - "wires": [ - [ - "6d471a5210505276" - ] - ] - }, - { - "id": "e8e488a6dd5d0b33", - "type": "ui_template", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "name": "Download", - "order": 5, - "width": 3, - "height": 1, - "format": "\n
Download\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 900, - "y": 260, - "wires": [ - [] - ] - }, - { - "id": "0c387c0291d6c131", - "type": "function", - "z": "4981d84ef1a366d1", - "name": "msg", - "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 750, - "y": 260, - "wires": [ - [ - "e8e488a6dd5d0b33" - ] - ] - }, - { - "id": "e5f38b4a07a5e278", - "type": "link in", - "z": "4981d84ef1a366d1", - "name": "", - "links": [ - "960912e90ba5b5bc" - ], - "x": 655, - "y": 220, - "wires": [ - [ - "834046a4.647938" - ] - ] - }, - { - "id": "e434ef42bd6b92e8", - "type": "ui_template", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "name": "upload2", - "order": 6, - "width": 3, - "height": 1, - "format": "upload", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 280, - "y": 420, - "wires": [ - [ - "f6bd1a04.470838", - "bfc01f26.c32cf" - ] - ] - }, - { - "id": "c46e10b9c201913e", - "type": "ui_template", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "name": "refresh", - "order": 1, - "width": 3, - "height": 1, - "format": "refresh{{msg.text}}", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 160, - "y": 180, - "wires": [ - [ - "372e95797a3f2f3b", - "4d99c601c9881680" - ] - ] - }, - { - "id": "d5d840183025d91b", - "type": "ui_template", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "name": "del set", - "order": 8, - "width": 2, - "height": 1, - "format": "delete set", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 270, - "y": 380, - "wires": [ - [ - "2806bf08ea21216d" - ] - ] - }, - { - "id": "ab9e90ab5a53a0dd", - "type": "ui_template", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "name": "del ", - "order": 9, - "width": 2, - "height": 1, - "format": "delete all", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 270, - "y": 340, - "wires": [ - [ - "f4e9a4bd79b4221f" - ] - ] - }, - { - "id": "478994f671a3907d", - "type": "ui_template", - "z": "4981d84ef1a366d1", - "group": "db43d646.2074c8", - "name": "combine", - "order": 7, - "width": 2, - "height": 1, - "format": "combine", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 280, - "y": 500, - "wires": [ - [ - "51bfd0fb7b1d292e" - ] - ] - }, - { - "id": "189c1eed09624a7b", - "type": "function", - "z": "4981d84ef1a366d1", - "name": "combine", - "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 540, - "wires": [ - [ - "1493398979a63775" - ] - ] - }, - { - "id": "51bfd0fb7b1d292e", - "type": "function", - "z": "4981d84ef1a366d1", - "name": "combine", - "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 420, - "y": 500, - "wires": [ - [] - ] - }, - { - "id": "da325be8e74179be", - "type": "python3-function", - "z": "4981d84ef1a366d1", - "name": "combine", - "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\n#set 1 is larger and to be merged into\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','combining ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", - "outputs": 1, - "x": 560, - "y": 540, - "wires": [ - [ - "ed35109311335099" - ] - ] - }, - { - "id": "ed35109311335099", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "", - "mode": "link", - "links": [ - "809c9427e14e2448", - "2f4c0f98.dee2" - ], - "x": 655, - "y": 540, - "wires": [] - }, - { - "id": "1493398979a63775", - "type": "ui_toast", - "z": "4981d84ef1a366d1", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "Combine", - "x": 420, - "y": 540, - "wires": [ - [ - "da325be8e74179be" - ] - ] - }, - { - "id": "ada1b6f7cccc9344", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "combine", - "mode": "link", - "links": [ - "6dd356510c446cf4" - ], - "x": 835, - "y": 180, - "wires": [] - }, - { - "id": "6dd356510c446cf4", - "type": "link in", - "z": "4981d84ef1a366d1", - "name": "combine", - "links": [ - "ada1b6f7cccc9344" - ], - "x": 175, - "y": 540, - "wires": [ - [ - "189c1eed09624a7b" - ] - ] - }, - { - "id": "b42e061fb1f1f3d7", - "type": "link out", - "z": "4981d84ef1a366d1", - "name": "", - "mode": "link", - "links": [ - "397ab7f44b893c89" - ], - "x": 435, - "y": 140, - "wires": [] - }, - { - "id": "52858b4eceacc902", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "", - "group": "3b4bd36726be16d5", - "order": 4, - "width": 2, - "height": 1, - "passthru": false, - "label": "Terms", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 90, - "y": 300, - "wires": [ - [ - "f99ec8781a33ec7d" - ] - ] - }, - { - "id": "7dc39bd847d16ded", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Agree", - "cancel": "Disagree", - "raw": true, - "className": "", - "topic": "", - "name": "", - "x": 410, - "y": 300, - "wires": [ - [ - "5f849178998d9082" - ] - ] - }, - { - "id": "cc3cb10f2ea3f8b8", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "blink Light1", - "func": "import RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nfrom OpenScan import ringlight\nfrom time import sleep\n\ndelay = 0.1\nringlight(2,False)\n\nfor i in range (5):\n ringlight(1,True)\n sleep(delay)\n ringlight(1,False)\n sleep(delay)", - "outputs": 1, - "x": 310, - "y": 420, - "wires": [ - [] - ] - }, - { - "id": "d114f4d4d7f31981", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "reboot", - "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('sudo reboot')\n", - "outputs": 1, - "x": 290, - "y": 380, - "wires": [ - [] - ] - }, - { - "id": "f52d4d86b39aeb6b", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "shutdown", - "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('sudo shutdown -h now')", - "outputs": 1, - "x": 300, - "y": 460, - "wires": [ - [] - ] - }, - { - "id": "15a0a2f431ce55c3", - "type": "comment", - "z": "017bd4e4a428bee5", - "name": "General Settings", - "info": "", - "x": 120, - "y": 260, - "wires": [] - }, - { - "id": "87a403b9a09aa38d", - "type": "comment", - "z": "017bd4e4a428bee5", - "name": "Network", - "info": "", - "x": 120, - "y": 560, - "wires": [] - }, - { - "id": "ca4afadb5b21751f", - "type": "comment", - "z": "017bd4e4a428bee5", - "name": "Info Texts", - "info": "", - "x": 100, - "y": 120, - "wires": [] - }, - { - "id": "2a0f9919.4c9a86", - "type": "comment", - "z": "017bd4e4a428bee5", - "name": "OpenScanCloud", - "info": "", - "x": 140, - "y": 880, - "wires": [] - }, - { - "id": "27c6b221c90ed9e1", - "type": "exec", - "z": "017bd4e4a428bee5", - "command": "sudo iwlist wlan0 scan | grep ESSID | sed 's/ESSID://g;s/\"//g;s/^ *//;s/ *$//'", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "scan", - "x": 270, - "y": 720, - "wires": [ - [ - "b05cf92302a5c112" - ], - [ - "e9677b85856b5873" - ], - [] - ] - }, - { - "id": "b05cf92302a5c112", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "WIFI", - "func": "msg['options']=[]\n\nfor i in msg['payload'].split('\\n'):\n if i not in msg['options'] and i!=\"\":\n msg['options'].append(i)\n \nif len(msg['options']) != 0:\n msg['enabled']=True\n\nreturn msg", - "outputs": 1, - "x": 390, - "y": 700, - "wires": [ - [ - "59c9f67283ba1709" - ] - ] - }, - { - "id": "da5ddaf4cc25b8c8", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "search", - "group": "3b4bd36726be16d5", - "order": 11, - "width": 3, - "height": 1, - "passthru": false, - "label": "Search Wifi", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "true", - "payloadType": "bool", - "topic": "", - "topicType": "str", - "x": 110, - "y": 660, - "wires": [ - [ - "27c6b221c90ed9e1", - "51521bc6eb44cde5" - ] - ] - }, - { - "id": "59c9f67283ba1709", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "", - "label": "", - "tooltip": "", - "place": "Select Wifi", - "group": "3b4bd36726be16d5", - "order": 14, - "width": 6, - "height": 1, - "passthru": true, - "multiple": false, - "options": [], - "payload": "", - "topic": "", - "topicType": "str", - "className": "", - "x": 540, - "y": 660, - "wires": [ - [ - "2bb52656f9554dab" - ] - ] - }, - { - "id": "b2d7d6a730f7dca6", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Reset Wifi", - "group": "3b4bd36726be16d5", - "order": 12, - "width": 3, - "height": 1, - "passthru": false, - "label": "Reset Wifi", - "tooltip": "", - "color": "red", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "Delete all prior wifi connections? (You will need to reconnect to the OpenScan device by Ethernet or manually modify the wpa_supplicant.conf)", - "payloadType": "str", - "topic": "", - "topicType": "str", - "x": 130, - "y": 820, - "wires": [ - [ - "78985ac6d3bcdf60" - ] - ] - }, - { - "id": "c3b8faac9ebb2c80", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "Reset Wifi", - "func": "from time import sleep\n\nif msg['payload']!=\"Yes\":\n return\n\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('sudo mv '+ temp_dir + ' ' + wpa_dir)\nos.system('sudo wpa_cli -i wlan0 reconfigure')\nsleep(3)\nos.system('sudo systemctl restart nodered')\nreturn msg", - "outputs": 1, - "x": 460, - "y": 820, - "wires": [ - [] - ] - }, - { - "id": "78985ac6d3bcdf60", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 290, - "y": 820, - "wires": [ - [ - "c3b8faac9ebb2c80" - ] - ] - }, - { - "id": "4f7f49b12c2d2572", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "add Wifi", - "func": "from time import sleep\nsleep(0.1)\n\nos.system('sudo wpa_cli -i wlan0 reconfigure')\n\nreturn msg", - "outputs": 1, - "x": 1340, - "y": 680, - "wires": [ - [] - ] - }, - { - "id": "ebcc98685059b9d4", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "password", - "x": 800, - "y": 660, - "wires": [ - [ - "68204a14528ab842" - ] - ] - }, - { - "id": "68204a14528ab842", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "if msg['payload'] == 'Cancel':\n return\n\nmsg['password'] = msg['payload']\nmsg['payload']='Enter country code (ISO 3166-1 alpha-2, see: Wikipedia)'\n\n\nreturn msg", - "outputs": 1, - "x": 930, - "y": 660, - "wires": [ - [ - "852edf901bdec9c5" - ] - ] - }, - { - "id": "852edf901bdec9c5", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Save", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "country", - "x": 1060, - "y": 660, - "wires": [ - [ - "1b09d634e3d9357b" - ] - ] - }, - { - "id": "1b09d634e3d9357b", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "modWPA", - "func": "if msg['payload'] == 'Cancel':\n return\n\nif len(msg['payload'])!=2:\n msg['payload'] = 'invalid country code'\n return msg,None\n\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\ntemp_dir = '/home/pi/OpenScan/tmp/wpa'\n\ncode = msg['payload'].upper()\nssid = msg['ssid']\npassword = msg['password']\n\nif len(code) != 2:\n msg['topic'] = 'ERROR'\n msg['payload'] = 'invalid country code (see ISO 3166-1 alpha-2)'\n return msg\n\nwith open(wpa_dir, 'r') as file:\n for i in file.readlines():\n if 'country=' in i:\n code_old=i.split('country=')[1][0:2]\n break\n\nwith open(wpa_dir, 'r') as file:\n wpa = file.read()\n if ssid in wpa:\n msg['topic'] = 'ERROR'\n msg['payload'] = 'Network already exists! If you have trouble connecting, please consider resetting the saved Wifi connections.'\n return msg\n wpa=wpa.replace('country=' + code_old, 'country=' + code)\n wpa=wpa + '\\nnetwork={\\n priority=10\\n ssid=\"'+ssid+'\"\\n psk=\"'+password+'\"\\n}\\n'\n\nwith open(temp_dir,'w+') as file:\n file.write(wpa)\nos.system('sudo mv '+temp_dir + ' ' + wpa_dir)\n\nmsg['topic'] = 'Updating Wifi'\nmsg['payload'] = 'reconnecting might take a moment'\nreturn msg,msg\n", - "outputs": 2, - "x": 1200, - "y": 660, - "wires": [ - [ - "03732a7d3b0c95aa" - ], - [ - "4f7f49b12c2d2572" - ] - ] - }, - { - "id": "03732a7d3b0c95aa", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1350, - "y": 640, - "wires": [ - [] - ] - }, - { - "id": "e97d17c6590138e2", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Cloud", - "group": "3b4bd36726be16d5", - "order": 2, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Terms

Please read the terms of use to understand what will happen to your data, when using the OpenScanCloud service.

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 250, - "y": 120, - "wires": [ - [ - "f304680180a23479" - ] - ] - }, - { - "id": "1969c709ef2fd1d5", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "order": 7, - "width": 0, - "height": 0, - "name": "", - "label": "Credit (GB):", - "format": "{{msg.credit}}", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 1140, - "wires": [] - }, - { - "id": "397ab7f44b893c89", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "OSCparameters", - "links": [ - "960912e90ba5b5bc", - "9c51aa678f16980f", - "b42e061fb1f1f3d7" - ], - "x": 465, - "y": 1140, - "wires": [ - [ - "a7fd00943edc380b" - ] - ] - }, - { - "id": "bf6d941ad307ce22", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 270, - "y": 960, - "wires": [ - [ - "f22dfef37d5de773" - ] - ] - }, - { - "id": "f22dfef37d5de773", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", - "outputs": 2, - "x": 410, - "y": 960, - "wires": [ - [ - "54602ee49ca022e7" - ], - [ - "1505f3e72f971081" - ] - ] - }, - { - "id": "1505f3e72f971081", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 1000, - "wires": [ - [] - ] - }, - { - "id": "54602ee49ca022e7", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 960, - "wires": [ - [ - "f9efcb87b74abbd4" - ] - ] - }, - { - "id": "510dbe4d76253bd6", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "SUBMIT", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 830, - "y": 960, - "wires": [ - [ - "600b2306caed1640" - ] - ] - }, - { - "id": "600b2306caed1640", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", - "outputs": 1, - "x": 970, - "y": 960, - "wires": [ - [ - "bbad1ab5f8f63fb7" - ] - ] - }, - { - "id": "d34cd203725bac15", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Register", - "group": "3b4bd36726be16d5", - "order": 5, - "width": 2, - "height": 1, - "passthru": false, - "label": "Register", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "Please enter your email address:", - "payloadType": "str", - "topic": "Requesting an OpenScanCloud Token", - "topicType": "str", - "x": 120, - "y": 960, - "wires": [ - [ - "bf6d941ad307ce22" - ] - ] - }, - { - "id": "bbad1ab5f8f63fb7", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1110, - "y": 960, - "wires": [ - [] - ] - }, - { - "id": "a7fd00943edc380b", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n return msg\nexcept:\n pass", - "outputs": 1, - "x": 590, - "y": 1140, - "wires": [ - [ - "1969c709ef2fd1d5" - ] - ] - }, - { - "id": "f99ec8781a33ec7d", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 300, - "wires": [ - [ - "7dc39bd847d16ded" - ] - ] - }, - { - "id": "5f849178998d9082", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "if(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 300, - "wires": [ - [] - ] - }, - { - "id": "725fd0cab0bddc0e", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 620, - "wires": [ - [ - "49259adad52fc214" - ] - ] - }, - { - "id": "49259adad52fc214", - "type": "ui_text_input", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Hostname", - "tooltip": "", - "group": "3b4bd36726be16d5", - "order": 13, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "Change hostname to:", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 550, - "y": 620, - "wires": [ - [ - "8001f7c361de7d8c" - ] - ] - }, - { - "id": "51521bc6eb44cde5", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 660, - "wires": [ - [ - "59c9f67283ba1709" - ] - ] - }, - { - "id": "2bb52656f9554dab", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "ssid = msg.payload\nmsg.topic = 'Add wifi network (' + ssid + ')'\nmsg.payload = 'Enter Wifi password:'\nmsg.ssid = ssid\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 670, - "y": 660, - "wires": [ - [ - "ebcc98685059b9d4" - ] - ] - }, - { - "id": "ebce67b739d1891f", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "chk/change hostname", - "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n pass\n\nwith open('/etc/hostname', 'r') as file:\n old_hostname = file.read().replace('\\n','')\n\nhostname = msg['hostname']\nif len(hostname) < 4 :\n msg['payload'] = ' '\n msg['topic'] = 'ERROR - Hostname NOT changed'\n return msg\n \n\nwith open('/etc/hostname', 'w+') as file:\n file.write(hostname)\nos.system('echo ' + hostname + ' | sudo tee /etc/hostname')\nwith open('/etc/hosts', 'r') as file:\n temp = file.read()\ntemp = temp.replace(old_hostname,hostname)\nwith open('/etc/hosts', 'w') as file:\n file.write(temp)\nos.system('sudo hostnamectl set-hostname ' + hostname)\nos.system('sudo systemctl restart avahi-daemon')\nsave('hostname',hostname)\nmsg['payload'] = hostname\nmsg['topic'] = 'Success - Hostname changed'\nreturn msg\n", - "outputs": 1, - "x": 1160, - "y": 620, - "wires": [ - [ - "03732a7d3b0c95aa" - ] - ] - }, - { - "id": "667ac2aba819f506", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "Confirm", - "x": 940, - "y": 620, - "wires": [ - [ - "ebce67b739d1891f" - ] - ] - }, - { - "id": "8001f7c361de7d8c", - "type": "change", - "z": "017bd4e4a428bee5", - "name": "", - "rules": [ - { - "t": "set", - "p": "hostname", - "pt": "msg", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 730, - "y": 620, - "wires": [ - [ - "667ac2aba819f506" - ] - ] - }, - { - "id": "9bb0adbd716ce347", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "reboot", - "links": [ - "16c76929f88df841", - "fe3a855fee9e28c6", - "d663dd83d71b8693" - ], - "x": 175, - "y": 380, - "wires": [ - [ - "d114f4d4d7f31981", - "cc3cb10f2ea3f8b8" - ] - ] - }, - { - "id": "fc9abb94c35eec56", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "shutdown", - "links": [ - "597bfb653e8cddbf" - ], - "x": 175, - "y": 460, - "wires": [ - [ - "cc3cb10f2ea3f8b8", - "f52d4d86b39aeb6b" - ] - ] - }, - { - "id": "f9efcb87b74abbd4", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 690, - "y": 960, - "wires": [ - [ - "510dbe4d76253bd6" - ] - ] - }, - { - "id": "adc206aa8edd1e41", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "OSC", - "group": "db43d646.2074c8", - "order": 2, - "width": 3, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Files&Cloud

Refresh

You can refresh the status of the processing of your files in the OpenScanCloud. Make sure to read and agree the terms of use (in settings menu) before using the OpenScanCloud. Do not spam this button, as this might lead to temporary/permanent suspension of your IP address.

The status (in the table) of the individual sets in the file list will be updated to one of the following:

Created - you started the upload of your image set. If you are stuck on this status, please try to restart the upload.

Initialized - all files have been uploaded and processing will start as soon as possible

File approved - the server received and verified your files

Processing started - your files are currently being processed

Processing failed - there are various reasons why processing might fail. Please check the email for more details or contact me at cloud@openscan.eu

processing done - check your email, where you should find a link to the 3d model :)

Status (on the right column)

Indicates, what the device is currently up to.

Refreshing - updating all image set's status

Uploading - while transferring the image set to the OpenScanCloud servers. If the upload freezes, be patient. If nothing happens, reboot the device and restart the upload.

Project started - when the upload of a set was successful

Zipping - files larger then 200mb have to be split and re-zipped before uploading to the OpenScanCloud, the process might take a while depending on the filesize.

Combining - two sets into one might take up to a minute. 

Set

select a set from the file list by clicking on a row in the table

Download

Download the selected set from the OpenScan device to your computer/mobile/tablet

Upload

Upload the selected file to the OpenScanCloud

Combine

In order to combine two sets, select one set. Click the combine button and select the second set. A pop-up will appear, and you can confirm the operation. All images from the two sets will be merged into one set. The original image sets will be deleted!

Delete Set/All

Please keep in mind, that the memory of the SD card is relatively small, and thus you will have to delete individual or all photo sets from time to time.

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 470, - "y": 160, - "wires": [ - [ - "f304680180a23479" - ] - ] - }, - { - "id": "e9677b85856b5873", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "Reset rfkill", - "func": "from os import system\nif \"Interface doesn't support scanning\" in msg['payload']:\n system('rfkill unblock all')\n system('sudo ifconfig wlan0 up')\n return msg", - "outputs": 1, - "x": 410, - "y": 740, - "wires": [ - [] - ] - }, - { - "id": "9b2bc9849aee310b", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "changeHostname", - "links": [ - "ec2db55a99bbe3ee", - "d5175561293ef490", - "960912e90ba5b5bc" - ], - "x": 855, - "y": 580, - "wires": [ - [ - "8b9e3781511e9231" - ] - ] - }, - { - "id": "8b9e3781511e9231", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "chk", - "func": "with open('/etc/hostname', 'r') as file:\n old_hostname = file.read().replace('\\n','')\nif old_hostname == 'raspberrypi':\n msg['hostname'] = 'openscan'\n msg['payload'] = 'OK'\n return msg", - "outputs": 1, - "x": 950, - "y": 580, - "wires": [ - [ - "ebce67b739d1891f" - ] - ] - }, - { - "id": "65b38bfeb3fee710", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 175, - "y": 420, - "wires": [ - [ - "cc3cb10f2ea3f8b8" - ] - ] - }, - { - "id": "d3fc91d87d5d5f62", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 155, - "y": 620, - "wires": [ - [ - "725fd0cab0bddc0e" - ] - ] - }, - { - "id": "cc9c4092edeb43cc", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc" - ], - "x": 155, - "y": 700, - "wires": [ - [ - "27c6b221c90ed9e1" - ] - ] - }, - { - "id": "80bccc884b0be297", - "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 1435, - "y": 300, - "wires": [] - }, - { - "id": "25426d3582cc1236", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Enter Tk", - "group": "3b4bd36726be16d5", - "order": 3, - "width": 2, - "height": 1, - "passthru": false, - "label": "Enter Token", - "tooltip": "testtesttest", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "Please enter your OpenScanCloud Token:", - "payloadType": "str", - "topic": "Token", - "topicType": "str", - "x": 120, - "y": 1060, - "wires": [ - [ - "c690fed61878ce83" - ] - ] - }, - { - "id": "c690fed61878ce83", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 270, - "y": 1060, - "wires": [ - [ - "781f672b78ea70b2" - ] - ] - }, - { - "id": "781f672b78ea70b2", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "save", - "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\ntoken = msg['payload']\n\nif len(msg['payload']) >=14:\n try:\n r = OpenScanCloud('getTokenInfo', {'token':token})\n if r.status_code != 200:\n msg['topic'] = 'Error'\n msg['payload'] = 'Invalid Token'\n return msg \n \n msg1 = r.json()\n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n save('token',token)\n msg['topic'] = 'Success'\n msg['payload'] = 'Token verified and saved'\n except:\n msg['topic'] = 'Error'\n msg['payload'] = 'Could not verify token, please check your internet connection.'\n return msg \n\n\nelse:\n msg['topic'] = 'Error'\n msg['payload'] = 'Invalid tokenformat'\n\nreturn msg", - "outputs": 1, - "x": 430, - "y": 1060, - "wires": [ - [ - "5e4b3bdb0a26052d", - "4faf2fbd3cf6aa3a", - "a7fd00943edc380b" - ] - ] - }, - { - "id": "6d2c65d7e1d928ce", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "order": 6, - "width": 0, - "height": 0, - "name": "", - "label": "Token", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 710, - "y": 1100, - "wires": [] - }, - { - "id": "5e4b3bdb0a26052d", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "text", - "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", - "outputs": 1, - "x": 590, - "y": 1100, - "wires": [ - [ - "6d2c65d7e1d928ce" - ] - ] - }, - { - "id": "e0965e490d53617f", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "token", - "links": [ - "960912e90ba5b5bc" - ], - "x": 465, - "y": 1100, - "wires": [ - [ - "5e4b3bdb0a26052d" - ] - ] - }, - { - "id": "4faf2fbd3cf6aa3a", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 610, - "y": 1060, - "wires": [ - [] - ] - }, - { - "id": "36b3b36c399ac7db", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", - "outputs": 2, - "x": 310, - "y": 1380, - "wires": [ - [ - "1d9f24f41817a2de" - ], - [ - "0c1d054fa7f2afe8" - ] - ] - }, - { - "id": "48cd023b07c39a94", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "order": 20, - "width": 0, - "height": 0, - "name": "", - "label": "Status:", - "format": "{{msg.status}}", - "layout": "row-spread", - "className": "", - "x": 170, - "y": 1300, - "wires": [] - }, - { - "id": "0c1d054fa7f2afe8", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\nfrom time import sleep\n\nsleep(1)\n\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter # new file counter\n\nreturn msg\n", - "outputs": 1, - "x": 470, - "y": 1400, - "wires": [ - [ - "1d9f24f41817a2de", - "7097687ddcc4fa8e" - ] - ] - }, - { - "id": "612a7556ab11cf7d", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "", - "links": [ - "960912e90ba5b5bc" - ], - "x": 75, - "y": 1340, - "wires": [ - [ - "e447af84ecc540ad", - "72ca6c281c43acd7", - "dadf823225aa34c4", - "9df2481a03f24d0a" - ] - ] - }, - { - "id": "7097687ddcc4fa8e", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "NO", - "cancel": "YES", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 630, - "y": 1420, - "wires": [ - [ - "f9fe26a38501bcad", - "77859c0059f8a49e" - ] - ] - }, - { - "id": "9112e8b2865ea436", - "type": "link in", - "z": "017bd4e4a428bee5", - "name": "update status", - "links": [ - "1d9f24f41817a2de", - "26dae88a383eee97" - ], - "x": 75, - "y": 1300, - "wires": [ - [ - "48cd023b07c39a94" - ] - ] - }, - { - "id": "1d9f24f41817a2de", - "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", - "mode": "link", - "links": [ - "9112e8b2865ea436" - ], - "x": 575, - "y": 1380, - "wires": [] - }, - { - "id": "f9fe26a38501bcad", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", - "outputs": 1, - "x": 800, - "y": 1440, - "wires": [ - [ - "26dae88a383eee97", - "d663dd83d71b8693" - ] - ] - }, - { - "id": "77859c0059f8a49e", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "msg", - "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 770, - "y": 1400, - "wires": [ - [ - "26dae88a383eee97" - ] - ] - }, - { - "id": "26dae88a383eee97", - "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", - "mode": "link", - "links": [ - "9112e8b2865ea436" - ], - "x": 925, - "y": 1400, - "wires": [] - }, - { - "id": "e447af84ecc540ad", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 170, - "y": 1380, - "wires": [ - [ - "36b3b36c399ac7db" - ] - ] - }, - { - "id": "d663dd83d71b8693", - "type": "link out", - "z": "017bd4e4a428bee5", - "name": "", - "mode": "link", - "links": [ - "9bb0adbd716ce347" - ], - "x": 925, - "y": 1440, - "wires": [] - }, - { - "id": "444acd32e7578254", - "type": "python3-function", - "z": "017bd4e4a428bee5", - "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'mini'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\ntry:\n del msg[scope]\nexcept:\n pass\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/Arducam.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/Arducam.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/OpenScan.py'\nmsg[scope]['3']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/config.txt'\nmsg[scope]['4']['dst'] = '/boot/config.txt'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/flows.json'\nmsg[scope]['5']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['6'] = {}\nmsg[scope]['6']['src'] = scope + '/settings.js'\nmsg[scope]['6']['dst'] = '/root/.node-red/settings.js'\n\nmsg[scope]['7'] = {}\nmsg[scope]['7']['src'] = 'files/logo.jpg'\nmsg[scope]['7']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", - "outputs": 1, - "x": 280, - "y": 1780, - "wires": [ - [ - "7f097823a90facb6" - ] - ] - }, - { - "id": "7f097823a90facb6", - "type": "debug", - "z": "017bd4e4a428bee5", - "name": "", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 430, - "y": 1780, - "wires": [] - }, - { - "id": "e547e40ff805742b", - "type": "inject", - "z": "017bd4e4a428bee5", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 120, - "y": 1780, - "wires": [ - [ - "444acd32e7578254" - ] - ] - }, - { - "id": "5fe2d831c3ab1cf4", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 570, - "y": 1340, - "wires": [ - [] - ] - }, - { - "id": "154716c51aae2b87", - "type": "ui_switch", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Auto-check update availability", - "tooltip": "", - "group": "3b4bd36726be16d5", - "order": 21, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 370, - "y": 1340, - "wires": [ - [ - "5fe2d831c3ab1cf4" - ] - ] - }, - { - "id": "72ca6c281c43acd7", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 170, - "y": 1340, - "wires": [ - [ - "154716c51aae2b87" - ] - ] - }, - { - "id": "a0e996cbd2b18363", - "type": "comment", - "z": "017bd4e4a428bee5", - "name": "Updates", - "info": "", - "x": 120, - "y": 1240, - "wires": [] - }, - { - "id": "0dcf979b126c3e33", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "order": 1, - "width": 4, - "height": 1, - "name": "", - "label": "", - "format": "OPENSCANCLOUD", - "layout": "col-center", - "className": "", - "x": 290, - "y": 900, - "wires": [] - }, - { - "id": "50ab351d92165de8", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "order": 9, - "width": 4, - "height": 1, - "name": "", - "label": "", - "format": "NETWORK SETTINGS", - "layout": "col-center", - "className": "", - "x": 270, - "y": 560, - "wires": [] - }, - { - "id": "726819d40397f3ce", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Wifi", - "group": "3b4bd36726be16d5", - "order": 10, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Network Settings

Hostname

The device can be accessed through any browser in the same network. Therefore, you can either enter the device's IP address or the given hostname. The standard name is 'openscan' but it is highly recommended to change the name, when using multiple devices (e.g. 'openscan1', 'openscan2' ...)

Select Wifi

After booting, the device will automatically search for available wireless networks and create a list. You can connect to a given network by entering the wifi password and country code. To find the right two-character country code, see the following list: ISO 3166 Country Code on Wikipedia

Search Wifi

You can manually refresh the list of available networks by pressing this button.

Reset Wifi

Delete the list of known wireless networks (and passwords) and reset the default. After this step, you will either need to use Ethernet or a modified wpa_supplicant.conf file. (see glennklockwood.com for more details about the wpa_supplicant.conf file, which has to be manually created and placed into the /boot/ directory of the sd-card)

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 250, - "y": 160, - "wires": [ - [ - "f304680180a23479" - ] - ] - }, - { - "id": "d07e9c092f0855eb", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "order": 16, - "width": 4, - "height": 1, - "name": "", - "label": "", - "format": "UPDATES", - "layout": "col-center", - "className": "", - "x": 270, - "y": 1240, - "wires": [] - }, - { - "id": "a85de9dee94dc786", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Updates&LOG", - "group": "3b4bd36726be16d5", - "order": 17, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 280, - "y": 200, - "wires": [ - [ - "f304680180a23479" - ] - ] - }, - { - "id": "2968c5996fb6d98c", - "type": "ui_template", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "name": "Download LOG", - "order": 25, - "width": 6, - "height": 1, - "format": "\n
Download error log\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 140, - "y": 1520, - "wires": [ - [] - ] - }, - { - "id": "d66c08c5f0134cf3", - "type": "ui_template", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "name": "Check Updates", - "order": 18, - "width": 6, - "height": 1, - "format": "Check updates", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 140, - "y": 1420, - "wires": [ - [ - "36b3b36c399ac7db" - ] - ] - }, - { - "id": "f304680180a23479", - "type": "ui_toast", - "z": "017bd4e4a428bee5", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": true, - "className": "", - "topic": "", - "name": "Info", - "x": 630, - "y": 120, - "wires": [ - [] - ] - }, - { - "id": "955f1e6794f368e2", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Scan", - "group": "7aaf184330605300", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Files&Cloud

Current Status

--READY-- - everything is okay and ready to go :)

Routine-preparing - before starting the routine some time might pass depending on the number of photos

Routine-stopping - manually ending the routine by pressing the stop button

Routine-Photo X/Y - Showing the progress of the routine

No Camera Found - please check the camera ribbon cable

Error: XXX - Please contact info@openscan.eu or post an issue on Github.com

Projectname

Each photo set will be saved using the following pattern  YYYY-MM-DD_hh-mm-ss_projectname.zip (e.g. 2022-04-05_12.12.12_toysoldier.zip). Keep your files organized by giving each set a new projectname. If not specified 'default' will be used.

Rotor

Moving the rotor by increments of 5°. Please make sure to start the routine with the camera in the horizontal position.

Turntable

Moving the turntable by increments of 15°.

Ringlight

Use the ring light for shadow-free illumination. It is highly recommended to use the polarizer in order to avoid reflections. Note, that the polarizer will absorb 75% of the light, so you might need to use both ring lights.

Photos

Set the number of photos for the current set. 60-120 photos should be more than enough for most objects. If the reconstruction fails or is very bad with 60 photos, increasing the number of photos will not help!

Shutter

Again: Less is more! If the value is too high, some areas might get overexposed and thus, the software will not be able to recognize the surface feature of the object. Here are some reference values:

- no polarizer: 5-20ms

- mostly white object,  with polarizer + one ringlight: 50-200ms

Crop X/Y

Make sure to use the right object holder to place the object in the middle of the screen. Try to crop as many unnecessary areas as possible. This will greatly lower the file size and resulting transfer and reconstruction times!

Start/Stop

Use the buttons to start/stop the routine

Reboot/Shutdown

In case of an error, try to restart the device. Always use the shutdown button before powering-off the device!

MF - Manual Focus

By default, the switch is 'off', which means that autofocus is active. For small objects, it might be necessary to use manual focus: activate the switch and set the focus by pressing + and - accordingly. The distance is measured between the camera lens and the focal plane (which should be in the center or slightly in front of the center of the object).

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 470, - "y": 200, - "wires": [ - [ - "f304680180a23479" - ] - ] - }, - { - "id": "dadf823225aa34c4", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadB", - "func": "var file = 'turntable_mode'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 170, - "y": 1620, - "wires": [ - [ - "60f6ee795446200d" - ] - ] - }, - { - "id": "60f6ee795446200d", - "type": "ui_switch", - "z": "017bd4e4a428bee5", - "name": "", - "label": "Turntable Mode", - "tooltip": "", - "group": "3b4bd36726be16d5", - "order": 26, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 320, - "y": 1620, - "wires": [ - [ - "227d89d897a8bdf5" - ] - ] - }, - { - "id": "227d89d897a8bdf5", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'turntable_mode'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 1620, - "wires": [ - [] - ] - }, - { - "id": "8b317478e9d905b8", - "type": "ui_button", - "z": "017bd4e4a428bee5", - "name": "Other Settings", - "group": "3b4bd36726be16d5", - "order": 24, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Network Settings

Hostname

The device can be accessed through any browser in the same network. Therefore, you can either enter the device's IP address or the given hostname. The standard name is 'openscan' but it is highly recommended to change the name, when using multiple devices (e.g. 'openscan1', 'openscan2' ...)

Select Wifi

After booting, the device will automatically search for available wireless networks and create a list. You can connect to a given network by entering the wifi password and country code. To find the right two-character country code, see the following list: ISO 3166 Country Code on Wikipedia

Search Wifi

You can manually refresh the list of available networks by pressing this button.

Reset Wifi

Delete the list of known wireless networks (and passwords) and reset the default. After this step, you will either need to use Ethernet or a modified wpa_supplicant.conf file. (see glennklockwood.com for more details about the wpa_supplicant.conf file, which has to be manually created and placed into the /boot/ directory of the sd-card)

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 280, - "y": 80, - "wires": [ - [] - ] - }, - { - "id": "814eaca62debe694", - "type": "ui_text", - "z": "017bd4e4a428bee5", - "group": "3b4bd36726be16d5", - "order": 23, - "width": 4, - "height": 1, - "name": "", - "label": "", - "format": "OTHER SETTINGS", - "layout": "col-center", - "className": "", - "x": 170, - "y": 1580, - "wires": [] - }, - { - "id": "9df2481a03f24d0a", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "loadS", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\n\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 170, - "y": 1660, - "wires": [ - [ - "ecba1ecce99e3968" - ] - ] - }, - { - "id": "f1de798f2a68e76e", - "type": "function", - "z": "017bd4e4a428bee5", - "name": "write", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 1660, - "wires": [ - [] - ] - }, - { - "id": "ecba1ecce99e3968", - "type": "ui_dropdown", - "z": "017bd4e4a428bee5", - "name": "Update_select", - "label": "", - "tooltip": "", - "place": "Select version", - "group": "3b4bd36726be16d5", - "order": 19, - "width": 0, - "height": 0, - "passthru": false, - "multiple": false, - "options": [ - { - "label": "Mini_simplified", - "value": "mini", - "type": "str" - }, - { - "label": "Advanced", - "value": "main", - "type": "str" - }, - { - "label": "Beta", - "value": "beta", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 320, - "y": 1660, - "wires": [ - [ - "f1de798f2a68e76e" - ] - ] - } -] \ No newline at end of file diff --git a/update/mini/settings.js b/update/mini/settings.js deleted file mode 100644 index 834f457..0000000 --- a/update/mini/settings.js +++ /dev/null @@ -1,489 +0,0 @@ -/** - * Node-RED Settings created at Mon, 24 Jan 2022 08:17:31 GMT - * - * It can contain any valid JavaScript code that will get run when Node-RED - * is started. - * - * Lines that start with // are commented out. - * Each entry should be separated from the entries above and below by a comma ',' - * - * For more information about individual settings, refer to the documentation: - * https://nodered.org/docs/user-guide/runtime/configuration - * - * The settings are split into the following sections: - * - Flow File and User Directory Settings - * - Security - * - Server Settings - * - Runtime Settings - * - Editor Settings - * - Node Settings - * - **/ - -module.exports = { - -/******************************************************************************* - * Flow File and User Directory Settings - * - flowFile - * - credentialSecret - * - flowFilePretty - * - userDir - * - nodesDir - ******************************************************************************/ - - /** The file containing the flows. If not set, defaults to flows_.json **/ - flowFile: "flows.json", - - /** By default, credentials are encrypted in storage using a generated key. To - * specify your own secret, set the following property. - * If you want to disable encryption of credentials, set this property to false. - * Note: once you set this property, do not change it - doing so will prevent - * node-red from being able to decrypt your existing credentials and they will be - * lost. - */ - credentialSecret: false, - - /** By default, the flow JSON will be formatted over multiple lines making - * it easier to compare changes when using version control. - * To disable pretty-printing of the JSON set the following property to false. - */ - flowFilePretty: true, - - /** By default, all user data is stored in a directory called `.node-red` under - * the user's home directory. To use a different location, the following - * property can be used - */ - //userDir: '/home/nol/.node-red/', - userDir: '/home/pi/OpenScan/settings/.node-red/', - /** Node-RED scans the `nodes` directory in the userDir to find local node files. - * The following property can be used to specify an additional directory to scan. - */ - //nodesDir: '/home/nol/.node-red/nodes', - -/******************************************************************************* - * Security - * - adminAuth - * - https - * - httpsRefreshInterval - * - requireHttps - * - httpNodeAuth - * - httpStaticAuth - ******************************************************************************/ - - /** To password protect the Node-RED editor and admin API, the following - * property can be used. See http://nodered.org/docs/security.html for details. - */ - //adminAuth: { - // type: "credentials", - // users: [{ - // username: "admin", - // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", - // permissions: "*" - // }] - //}, - - /** The following property can be used to enable HTTPS - * This property can be either an object, containing both a (private) key - * and a (public) certificate, or a function that returns such an object. - * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener - * for details of its contents. - */ - - /** Option 1: static object */ - //https: { - // key: require("fs").readFileSync('privkey.pem'), - // cert: require("fs").readFileSync('cert.pem') - //}, - - /** Option 2: function that returns the HTTP configuration object */ - // https: function() { - // // This function should return the options object, or a Promise - // // that resolves to the options object - // return { - // key: require("fs").readFileSync('privkey.pem'), - // cert: require("fs").readFileSync('cert.pem') - // } - // }, - - /** If the `https` setting is a function, the following setting can be used - * to set how often, in hours, the function will be called. That can be used - * to refresh any certificates. - */ - //httpsRefreshInterval : 12, - - /** The following property can be used to cause insecure HTTP connections to - * be redirected to HTTPS. - */ - //requireHttps: true, - - /** To password protect the node-defined HTTP endpoints (httpNodeRoot), - * including node-red-dashboard, or the static content (httpStatic), the - * following properties can be used. - * The `pass` field is a bcrypt hash of the password. - * See http://nodered.org/docs/security.html#generating-the-password-hash - */ - //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, - //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, - -/******************************************************************************* - * Server Settings - * - uiPort - * - uiHost - * - apiMaxLength - * - httpServerOptions - * - httpAdminRoot - * - httpAdminMiddleware - * - httpNodeRoot - * - httpNodeCors - * - httpNodeMiddleware - * - httpStatic - ******************************************************************************/ - - /** the tcp port that the Node-RED web server is listening on */ -// uiPort: process.env.PORT || 1880, -uiPort: process.env.PORT || 80, - /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. - * To listen on all IPv6 addresses, set uiHost to "::", - * The following property can be used to listen on a specific interface. For - * example, the following would only allow connections from the local machine. - */ - //uiHost: "127.0.0.1", - - /** The maximum size of HTTP request that will be accepted by the runtime api. - * Default: 5mb - */ - //apiMaxLength: '5mb', - - /** The following property can be used to pass custom options to the Express.js - * server used by Node-RED. For a full list of available options, refer - * to http://expressjs.com/en/api.html#app.settings.table - */ - //httpServerOptions: { }, - - /** By default, the Node-RED UI is available at http://localhost:1880/ - * The following property can be used to specify a different root path. - * If set to false, this is disabled. - */ - //httpAdminRoot: '/admin', -httpAdminRoot: '/editor', - /** The following property can be used to add a custom middleware function - * in front of all admin http routes. For example, to set custom http - * headers. It can be a single function or an array of middleware functions. - */ - // httpAdminMiddleware: function(req,res,next) { - // // Set the X-Frame-Options header to limit where the editor - // // can be embedded - // //res.set('X-Frame-Options', 'sameorigin'); - // next(); - // }, - - - /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. - * By default, these are served relative to '/'. The following property - * can be used to specifiy a different root path. If set to false, this is - * disabled. - */ - //httpNodeRoot: '/red-nodes', - - /** The following property can be used to configure cross-origin resource sharing - * in the HTTP nodes. - * See https://github.com/troygoode/node-cors#configuration-options for - * details on its contents. The following is a basic permissive set of options: - */ - //httpNodeCors: { - // origin: "*", - // methods: "GET,PUT,POST,DELETE" - //}, - - /** If you need to set an http proxy please set an environment variable - * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. - * For example - http_proxy=http://myproxy.com:8080 - * (Setting it here will have no effect) - * You may also specify no_proxy (or NO_PROXY) to supply a comma separated - * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk - */ - - /** The following property can be used to add a custom middleware function - * in front of all http in nodes. This allows custom authentication to be - * applied to all http in nodes, or any other sort of common request processing. - * It can be a single function or an array of middleware functions. - */ - //httpNodeMiddleware: function(req,res,next) { - // // Handle/reject the request, or pass it on to the http in node by calling next(); - // // Optionally skip our rawBodyParser by setting this to true; - // //req.skipRawBodyParser = true; - // next(); - //}, - - /** When httpAdminRoot is used to move the UI to a different root path, the - * following property can be used to identify a directory of static content - * that should be served at http://localhost:1880/. - */ - //httpStatic: '/home/nol/node-red-static/', -httpStatic: '/home/pi/OpenScan/', -/******************************************************************************* - * Runtime Settings - * - lang - * - logging - * - contextStorage - * - exportGlobalContextKeys - * - externalModules - ******************************************************************************/ - - /** Uncomment the following to run node-red in your preferred language. - * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko - * Some languages are more complete than others. - */ - // lang: "de", - - /** Configure the logging output */ - logging: { - /** Only console logging is currently supported */ - console: { - /** Level of logging to be recorded. Options are: - * fatal - only those errors which make the application unusable should be recorded - * error - record errors which are deemed fatal for a particular request + fatal errors - * warn - record problems which are non fatal + errors + fatal errors - * info - record information about the general running of the application + warn + error + fatal errors - * debug - record information which is more verbose than info + info + warn + error + fatal errors - * trace - record very detailed logging + debug + info + warn + error + fatal errors - * off - turn off all logging (doesn't affect metrics or audit) - */ - level: "info", - /** Whether or not to include metric events in the log output */ - metrics: false, - /** Whether or not to include audit events in the log output */ - audit: false - } - }, - - /** Context Storage - * The following property can be used to enable context storage. The configuration - * provided here will enable file-based context that flushes to disk every 30 seconds. - * Refer to the documentation for further options: https://nodered.org/docs/api/context/ - */ - //contextStorage: { - // default: { - // module:"localfilesystem" - // }, - //}, - - /** `global.keys()` returns a list of all properties set in global context. - * This allows them to be displayed in the Context Sidebar within the editor. - * In some circumstances it is not desirable to expose them to the editor. The - * following property can be used to hide any property set in `functionGlobalContext` - * from being list by `global.keys()`. - * By default, the property is set to false to avoid accidental exposure of - * their values. Setting this to true will cause the keys to be listed. - */ - exportGlobalContextKeys: false, - - /** Configure how the runtime will handle external npm modules. - * This covers: - * - whether the editor will allow new node modules to be installed - * - whether nodes, such as the Function node are allowed to have their - * own dynamically configured dependencies. - * The allow/denyList options can be used to limit what modules the runtime - * will install/load. It can use '*' as a wildcard that matches anything. - */ - externalModules: { - // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ - // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ - // palette: { /** Configuration for the Palette Manager */ - // allowInstall: true, /** Enable the Palette Manager in the editor */ - // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ - // allowList: [], - // denyList: [] - // }, - // modules: { /** Configuration for node-specified modules */ - // allowInstall: true, - // allowList: [], - // denyList: [] - // } - }, - - -/******************************************************************************* - * Editor Settings - * - disableEditor - * - editorTheme - ******************************************************************************/ - - /** The following property can be used to disable the editor. The admin API - * is not affected by this option. To disable both the editor and the admin - * API, use either the httpRoot or httpAdminRoot properties - */ - //disableEditor: false, - - /** Customising the editor - * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes - * for all available options. - */ - editorTheme: { - /** The following property can be used to set a custom theme for the editor. - * See https://github.com/node-red-contrib-themes/theme-collection for - * a collection of themes to chose from. - */ - //theme: "", - palette: { - /** The following property can be used to order the categories in the editor - * palette. If a node's category is not in the list, the category will get - * added to the end of the palette. - * If not set, the following default order is used: - */ - //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], - }, - projects: { - /** To enable the Projects feature, set this value to true */ - enabled: false, - workflow: { - /** Set the default projects workflow mode. - * - manual - you must manually commit changes - * - auto - changes are automatically committed - * This can be overridden per-user from the 'Git config' - * section of 'User Settings' within the editor - */ - mode: "manual" - } - }, - codeEditor: { - /** Select the text editor component used by the editor. - * Defaults to "ace", but can be set to "ace" or "monaco" - */ - lib: "ace", - options: { - /** The follow options only apply if the editor is set to "monaco" - * - * theme - must match the file name of a theme in - * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme - * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" - */ - theme: "vs", - /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. - * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html - */ - //fontSize: 14, - //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", - //fontLigatures: true, - } - } - }, - -/******************************************************************************* - * Node Settings - * - fileWorkingDirectory - * - functionGlobalContext - * - functionExternalModules - * - nodeMessageBufferMaxLength - * - ui (for use with Node-RED Dashboard) - * - debugUseColors - * - debugMaxLength - * - execMaxBufferSize - * - httpRequestTimeout - * - mqttReconnectTime - * - serialReconnectTime - * - socketReconnectTime - * - socketTimeout - * - tcpMsgQueueSize - * - inboundWebSocketTimeout - * - tlsConfigDisableLocalFiles - * - webSocketNodeVerifyClient - ******************************************************************************/ - - /** The working directory to handle relative file paths from within the File nodes - * defaults to the working directory of the Node-RED process. - */ - //fileWorkingDirectory: "", - - /** Allow the Function node to load additional npm modules directly */ - functionExternalModules: true, - - /** The following property can be used to set predefined values in Global Context. - * This allows extra node modules to be made available with in Function node. - * For example, the following: - * functionGlobalContext: { os:require('os') } - * will allow the `os` module to be accessed in a Function node using: - * global.get("os") - */ - functionGlobalContext: { - os:require('os'), - path:require('path'), - fs:require('fs'), - -}, - - /** The maximum number of messages nodes will buffer internally as part of their - * operation. This applies across a range of nodes that operate on message sequences. - * defaults to no limit. A value of 0 also means no limit is applied. - */ - //nodeMessageBufferMaxLength: 0, - - /** If you installed the optional node-red-dashboard you can set it's path - * relative to httpNodeRoot - * Other optional properties include - * readOnly:{boolean}, - * middleware:{function or array}, (req,res,next) - http middleware - * ioMiddleware:{function or array}, (socket,next) - socket.io middleware - */ - //ui: { path: "ui" }, -ui: { path: "" }, - /** Colourise the console output of the debug node */ - //debugUseColors: true, - - /** The maximum length, in characters, of any message sent to the debug sidebar tab */ - debugMaxLength: 1000, - - /** Maximum buffer size for the exec node. Defaults to 10Mb */ - //execMaxBufferSize: 10000000, - - /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ - //httpRequestTimeout: 120000, - - /** Retry time in milliseconds for MQTT connections */ - mqttReconnectTime: 15000, - - /** Retry time in milliseconds for Serial port connections */ - serialReconnectTime: 15000, - - /** Retry time in milliseconds for TCP socket connections */ - //socketReconnectTime: 10000, - - /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ - //socketTimeout: 120000, - - /** Maximum number of messages to wait in queue while attempting to connect to TCP socket - * defaults to 1000 - */ - //tcpMsgQueueSize: 2000, - - /** Timeout in milliseconds for inbound WebSocket connections that do not - * match any configured node. Defaults to 5000 - */ - //inboundWebSocketTimeout: 5000, - - /** To disable the option for using local files for storing keys and - * certificates in the TLS configuration node, set this to true. - */ - //tlsConfigDisableLocalFiles: true, - - /** The following property can be used to verify websocket connection attempts. - * This allows, for example, the HTTP request headers to be checked to ensure - * they include valid authentication information. - */ - //webSocketNodeVerifyClient: function(info) { - // /** 'info' has three properties: - // * - origin : the value in the Origin header - // * - req : the HTTP request - // * - secure : true if req.connection.authorized or req.connection.encrypted is set - // * - // * The function should return true if the connection should be accepted, false otherwise. - // * - // * Alternatively, if this function is defined to accept a second argument, callback, - // * it can be used to verify the client asynchronously. - // * The callback takes three arguments: - // * - result : boolean, whether to accept the connection or not - // * - code : if result is false, the HTTP error status to return - // * - reason: if result is false, the HTTP reason string to return - // */ - //}, -} diff --git a/update/main/OpenScan.py b/update/stable/OpenScan.py similarity index 59% rename from update/main/OpenScan.py rename to update/stable/OpenScan.py index b1b994a..681c78d 100644 --- a/update/main/OpenScan.py +++ b/update/stable/OpenScan.py @@ -1,5 +1,6 @@ basepath = '/home/pi/OpenScan/' from os.path import isfile +import os def load_bool(name): filename = basepath+'settings/'+name @@ -13,6 +14,74 @@ def load_bool(name): value = False return value +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + + return True + + def load_str(name): filename = basepath+'settings/'+name if not isfile(filename): @@ -64,12 +133,12 @@ def camera(cmd, msg = {}): except: return 400 -def motorrun(motor,angle): +def motorrun(motor,angle,ES_enable=False,ES_start_state = True): + #motor can be "rotor", "tt" or "extra" import RPi.GPIO as GPIO from time import sleep from math import cos msg = {'cmd':'set'} - camera('/ping', msg) GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) @@ -77,6 +146,7 @@ def motorrun(motor,angle): spr = load_int(motor + '_stepsperrotation') dirpin = load_int('pin_' + motor + '_dir') steppin = load_int('pin_' + motor +'_step') + ES_pin = load_int('pin_' + motor + '_endstop') dir = load_int(motor + '_dir') ramp = load_int(motor + '_accramp') acc = load_float(motor + '_acc') @@ -86,12 +156,23 @@ def motorrun(motor,angle): step_count=int(angle*spr/360) * dir GPIO.setup(dirpin, GPIO.OUT) GPIO.setup(steppin, GPIO.OUT) + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + if (step_count>0): GPIO.output(dirpin, GPIO.HIGH) if(step_count<0): GPIO.output(dirpin, GPIO.LOW) step_count=-step_count for x in range(step_count): + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + GPIO.output(steppin, GPIO.HIGH) if x<=ramp and x<=step_count/2: delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) @@ -108,7 +189,6 @@ def motorrun(motor,angle): def ringlight(number,state): import RPi.GPIO as GPIO msg = {'cmd':'set'} - camera('/ping', msg) pin = load_int('pin_ringlight' + str(number)) GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) @@ -195,3 +275,42 @@ def create_coordinates(angle_min, angle_max,point_count): point_count=point_count+1 return filtered + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/mini/config.txt b/update/stable/config.txt old mode 100644 new mode 100755 similarity index 95% rename from update/mini/config.txt rename to update/stable/config.txt index fad6e74..cc525ae --- a/update/mini/config.txt +++ b/update/stable/config.txt @@ -2,10 +2,8 @@ # http://rpf.io/configtxt # Some settings may impact device functionality. See link above for details - # uncomment if you get no picture on HDMI for a default "safe" mode #hdmi_safe=1 -hdmi_blanking=2 # uncomment the following to adjust overscan. Use positive numbers if console # goes off screen, and negative if there is too much border @@ -55,13 +53,13 @@ hdmi_blanking=2 dtparam=audio=on # Automatically load overlays for detected cameras -camera_auto_detect=0 +camera_auto_detect=1 # Automatically load overlays for detected DSI displays display_auto_detect=1 # Enable DRM VC4 V3D driver -#dtoverlay=vc4-kms-v3d +dtoverlay=vc4-kms-v3d max_framebuffers=2 # Disable compensation for displays with overscan @@ -73,14 +71,15 @@ disable_overscan=1 # (e.g. for USB device mode) or if USB support is not required. otg_mode=1 +[all] + [pi4] # Run as fast as firmware / board allows arm_boost=1 [all] - camera_auto_detect=0 gpu_mem=256 dtoverlay=vc4-fkms-v3d -dtoverlay=imx519,media-controller=0 dtoverlay=imx519 +#dtoverlay=imx519,media-controller=1 diff --git a/update/stable/fla.py b/update/stable/fla.py new file mode 100644 index 0000000..57f4660 --- /dev/null +++ b/update/stable/fla.py @@ -0,0 +1,351 @@ +from flask import Flask, make_response, jsonify, request, abort, redirect +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont +from time import sleep, time +import shutil +from OpenScan import load_int, load_float, load_bool, ringlight +import RPi.GPIO as GPIO +from math import sqrt +import os +import math +from skimage import io, feature, color, transform +import numpy as np +from scipy import ndimage +import socket + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +app = Flask(__name__) + +basedir = '/home/pi/OpenScan/' +timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + + +################################################################################################################### +@app.route('/shutdown', methods=['get']) +def shutdown(): + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + os.system('shutdown -h now') + + else: + return redirect("http://" + hostname, code=302) +################################################################################################################### +@app.route('/reboot', methods=['get']) +def reboot(): + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + + os.system('reboot -h') + else: + return redirect("http://" + hostname, code=302) +################################################################################################################### + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) + + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') + + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wrong aspect ratio! +# preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}) + preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}, controls ={"FrameDurationLimits": (1, 1000000)}) + +# preview_config = picam2.create_preview_configuration(main={"size": (2328, 1748)}) + capture_config = picam2.create_still_configuration(controls ={"FrameDurationLimits": (1, 1000000)}) + picam2.configure(preview_config) + picam2.controls.AnalogueGain = 1.0 + picam2.start() + return ({}, 200) + +################################################################################################################### +@app.route('/picam2_take_photo', methods=['get']) +def picam2_take_photo(): + starttime = time() + + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + img = picam2.capture_image() + + if cam_mode !=1: + img = img.convert('RGB') + w,h = img.size + + if cropx != 0 or cropy != 0: + img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) + + if rotation == 90: + img = img.transpose(Image.ROTATE_90) + elif rotation == 180: + img= img.transpose(Image.ROTATE_180) + elif rotation == 270: + img= img.transpose(Image.ROTATE_270) + + if load_bool("cam_mask"): + if cam_mode == 1: + downscale = 0.045*1.4 + else: + downscale = 0.1*1.4 + img = create_mask(img, downscale) + + if load_bool("cam_features") and not load_bool("cam_sharparea"): + img = plot_orb_keypoints(img) + + if load_bool("cam_sharparea") and not load_bool("cam_features"): + img2 = highlight_sharpest_areas(img) + img = overlay_mask(img, img2) + + if cam_mode != 1 and not load_bool("cam_sharparea") and not load_bool("cam_features"): + img = add_histo(img) + + img.save("/home/pi/OpenScan/tmp2/preview.jpg", quality=load_int('cam_jpeg_quality')) + print("total " + str(int(1000*(time()-starttime))) + "ms") + starttime = time() + + return ({}, 200) +################################################################################################################### +@app.route('/picam2_focus', methods=['get']) +def picam2_focus(): + focus = float(request.args.get('focus')) + picam2.set_controls({"AfMode": 0, "LensPosition": focus}) + return ({}, 200) +################################################################################################################### +@app.route('/picam2_af1', methods=['get']) +def picam2_af1(): + from libcamera import controls + + picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0, "AfRange":controls.AfRangeEnum.Macro}) + return ({}, 200) +################################################################################################################### +@app.route('/picam2_af2', methods=['get']) +def picam2_af2(): + picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0}) + return ({}, 200) + +################################################################################################################### +@app.route('/picam2_exposure', methods=['get']) +def picam2_exposure(): + exposure = int(request.args.get('exposure')) + picam2.controls.AnalogueGain = 1.0 + picam2.controls.ExposureTime = exposure + return ({}, 200) +################################################################################################################### +@app.route('/picam2_contrast', methods=['get']) +def picam2_contrast(): + contrast = float(request.args.get('contrast')) + picam2.controls.Contrast = contrast + return ({}, 200) +################################################################################################################### +@app.route('/picam2_saturation', methods=['get']) +def picam2_saturation(): + saturation = float(request.args.get('saturation')) + picam2.controls.Saturation = saturation + return ({}, 200) +################################################################################################################### +@app.route('/picam2_switch_mode', methods=['get']) +def picam2_switch_mode(): + global cam_mode + cam_mode = int(request.args.get('mode')) + if cam_mode == 1: + picam2.switch_mode(capture_config) + else: + picam2.switch_mode(preview_config) + return ({}, 200) +################################################################################################################### +@app.route('/picam2_show_mode', methods=['get']) +def picam2_show_mode(): + global cam_mode + return({"mode":cam_mode},200) +################################################################################################################### +@app.route('/picam2_af', methods=['get']) +def picam2_af(): + picam2.set_controls({"AfMode": 1 ,"AfTrigger": 0}) # --> wait 3-5s + return ({}, 200) + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + +if __name__ == '__main__': +# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) + app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/betaArdu/flows.json b/update/stable/flows.json.tmpl similarity index 89% rename from update/betaArdu/flows.json rename to update/stable/flows.json.tmpl index dc5f6a4..d2e3f3c 100644 --- a/update/betaArdu/flows.json +++ b/update/stable/flows.json.tmpl @@ -364,9 +364,29 @@ "hidden": false }, { - "id": "c82a5d8ffa9290d7", + "id": "6e339d87c7d5debe", "type": "ui_spacer", - "z": "481edaf6db5a7a54", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", "name": "spacer", "group": "365a30d0dfa83e95", "order": 8, @@ -374,9 +394,9 @@ "height": 1 }, { - "id": "1234e5ed37d97dbc", + "id": "2e08d4415665c939", "type": "ui_spacer", - "z": "481edaf6db5a7a54", + "z": "e43a27722b508115", "name": "spacer", "group": "365a30d0dfa83e95", "order": 9, @@ -384,9 +404,9 @@ "height": 1 }, { - "id": "0866b0ae68867006", + "id": "f8d8740dcbf499fb", "type": "ui_spacer", - "z": "481edaf6db5a7a54", + "z": "e43a27722b508115", "name": "spacer", "group": "365a30d0dfa83e95", "order": 11, @@ -394,9 +414,9 @@ "height": 1 }, { - "id": "e871dfbac232f86e", + "id": "7ac0cb556740d159", "type": "ui_spacer", - "z": "481edaf6db5a7a54", + "z": "e43a27722b508115", "name": "spacer", "group": "365a30d0dfa83e95", "order": 13, @@ -404,9 +424,9 @@ "height": 1 }, { - "id": "148d18b0fe8c75ea", + "id": "4de2414e29020c74", "type": "ui_spacer", - "z": "481edaf6db5a7a54", + "z": "e43a27722b508115", "name": "spacer", "group": "90223f7ddc082321", "order": 2, @@ -414,9 +434,9 @@ "height": 1 }, { - "id": "acec03936a857704", + "id": "ac8c60543cb04139", "type": "ui_spacer", - "z": "481edaf6db5a7a54", + "z": "e43a27722b508115", "name": "spacer", "group": "ac7409105cfecac6", "order": 3, @@ -424,44 +444,45 @@ "height": 1 }, { - "id": "6e339d87c7d5debe", + "id": "ce21673092264c38", "type": "ui_spacer", "z": "e43a27722b508115", "name": "spacer", - "group": "db43d646.2074c8", - "order": 1, - "width": 1, + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, "height": 1 }, { - "id": "33b6d7317d1524b8", + "id": "3f7b77f8a1675d27", "type": "ui_spacer", "z": "e43a27722b508115", "name": "spacer", - "group": "db43d646.2074c8", - "order": 3, - "width": 1, + "group": "12b719cba49817c9", + "order": 7, + "width": 4, "height": 1 }, { - "id": "bd749f72a1b5436c", + "id": "0799b02d12fc3a14", "type": "ui_spacer", "z": "e43a27722b508115", "name": "spacer", - "group": "8ab79a98e536e0d6", - "order": 3, + "group": "7a3279eea439bcdd", + "order": 25, "width": 6, "height": 1 }, { - "id": "61fa5f50a63f5bdd", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "12b719cba49817c9", + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", "order": 7, - "width": 4, - "height": 1 + "disp": true, + "width": "6", + "collapse": false, + "className": "" }, { "id": "bc4e2c03859196c3", @@ -549,7 +570,7 @@ "type": "function", "z": "e6f4d02efb300ea9", "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -614,7 +635,8 @@ "592ec13d8f8923a9", "cb40b9341bd22a28", "d1efcd5fa9d25785", - "da61581182b7299e" + "da61581182b7299e", + "2afb6a45c73fa244" ], "x": 645, "y": 60, @@ -808,7 +830,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -1094,7 +1116,8 @@ "592ec13d8f8923a9", "cb40b9341bd22a28", "d1efcd5fa9d25785", - "da61581182b7299e" + "da61581182b7299e", + "2afb6a45c73fa244" ], "x": 1015, "y": 540, @@ -1105,7 +1128,7 @@ "type": "python3-function", "z": "e6f4d02efb300ea9", "name": "motor run", - "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',100,True)\n\nmotorrun('tt',360,True)\nmotorrun('extra',360,True)", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", "outputs": 1, "x": 860, "y": 580, @@ -1157,7 +1180,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 600, + "x": 580, "y": 260, "wires": [ [] @@ -1452,8 +1475,7 @@ "d1b87196ae5373ed", "41e6a4649b6afbfb", "2fd24f8e8e9c08b7", - "85a268108250ba88", - "e78235c36be29dbf" + "85a268108250ba88" ] ] }, @@ -1576,21 +1598,6 @@ ] ] }, - { - "id": "2c58a1a66c4a8c11", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "preview", - "links": [ - "f20da2fc4978b7bf", - "296636b7467fc745" - ], - "x": 145, - "y": 800, - "wires": [ - [] - ] - }, { "id": "296636b7467fc745", "type": "link out", @@ -2122,7 +2129,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 300, + "x": 280, "y": 1000, "wires": [ [ @@ -2135,13 +2142,14 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system\nfrom os.path import isfile\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nangle_start = load_int('rotor_anglestart')\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['payload'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\n\n#for i in range (36):\n# coordinates.append((-25,10*i))\n\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\n#coordinates = [[45,0],[0,0],[-10,0]]\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nstarttime = time()\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef photo_stack():\n c2 = 0\n for f in focuslist:\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(c2+1))\n camera('/picam2_take_photo')\n camera('/picam2_focus?focus=' + str(f)) \n sleep(0.1)\n zip.write(temppath, projectname + '_' + str(counter) + \"-\" + str(c2) + \".jpg\")\n c2 += 1\n\ndef move_motor():\n sleep(1.5 * load_int('cam_shutter') / 1000000)\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n tt_thread.start()\n rotor_thread.start()\n tt_thread.join()\n rotor_thread.join()\n\n\ncounter2 = 0\n\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n \n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo_thread = threading.Thread(target=photo, args=(returning,))\n photo_thread.start()\n\n motor_thread = threading.Thread(target=move_motor)\n motor_thread.start()\n photo_thread.join()\n motor_thread.join()\n counter2 = returning[0]\n\n else:\n photo_stack()\n move_motor()\n \n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, "wires": [ [ - "1daf9e3a5bd5ab48" + "1daf9e3a5bd5ab48", + "795c85ad4f109567" ] ] }, @@ -2301,7 +2309,7 @@ "order": 4, "width": 7, "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -2637,20 +2645,19 @@ ] }, { - "id": "e78235c36be29dbf", + "id": "795c85ad4f109567", "type": "debug", "z": "481edaf6db5a7a54", - "name": "debug 9", + "name": "debug 5", "active": true, "tosidebar": true, "console": false, "tostatus": false, - "complete": "true", - "targetType": "full", + "complete": "false", "statusVal": "", "statusType": "auto", - "x": 500, - "y": 960, + "x": 620, + "y": 1000, "wires": [] }, { @@ -2665,8 +2672,7 @@ "wires": [ [ "f3662f8c7d3d7a2d", - "01e4783e148c6698", - "332d0b0957b80b33" + "01e4783e148c6698" ] ] }, @@ -2676,11 +2682,11 @@ "z": "80a3942785a26c29", "name": "filelist", "links": [ + "50eeb3e362f9027f", "960912e90ba5b5bc", "a4f09e25.02569", "ed35109311335099", - "fb13752beddee9f2", - "50eeb3e362f9027f" + "fb13752beddee9f2" ], "x": 355, "y": 220, @@ -3016,8 +3022,7 @@ "wires": [ [ "ea54fcc2.cfcc2", - "b42e061fb1f1f3d7", - "df2a071988c4c723" + "b42e061fb1f1f3d7" ], [ "6434e713f088012b" @@ -3151,7 +3156,7 @@ "type": "python3-function", "z": "80a3942785a26c29", "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if os.path.isdir(dir + i):\n shutil.rmtree(dir + i)\n else:\n os.remove(dir + i)\n\nreturn msg", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", "outputs": 1, "x": 690, "y": 340, @@ -3631,35 +3636,6 @@ ] ] }, - { - "id": "50020809fbd23673", - "type": "inject", - "z": "80a3942785a26c29", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 320, - "y": 260, - "wires": [ - [ - "ea54fcc2.cfcc2" - ] - ] - }, { "id": "01e4783e148c6698", "type": "ui_table", @@ -3744,40 +3720,6 @@ ] ] }, - { - "id": "df2a071988c4c723", - "type": "debug", - "z": "80a3942785a26c29", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 460, - "y": 80, - "wires": [] - }, - { - "id": "332d0b0957b80b33", - "type": "debug", - "z": "80a3942785a26c29", - "name": "debug 8", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 675, - "y": 59, - "wires": [] - }, { "id": "cb3437ec113e1b6f", "type": "ui_switch", @@ -4082,7 +4024,7 @@ "animate": false, "className": "", "x": 400, - "y": 460, + "y": 480, "wires": [ [ "f06a7bcad524e9f9" @@ -4097,7 +4039,7 @@ "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", "outputs": 2, "x": 820, - "y": 460, + "y": 480, "wires": [ [ "8750ad979e9ea246" @@ -4238,8 +4180,8 @@ "payloadType": "str", "topic": "topic", "topicType": "msg", - "x": 580, - "y": 200, + "x": 740, + "y": 260, "wires": [ [ "5fff689f9f8bc1ca" @@ -4691,7 +4633,7 @@ "name": "Motor", "info": "", "x": 90, - "y": 1620, + "y": 1740, "wires": [] }, { @@ -4701,7 +4643,7 @@ "name": "Camera", "info": "", "x": 90, - "y": 2260, + "y": 2500, "wires": [] }, { @@ -4711,7 +4653,7 @@ "name": "Pinout", "info": "", "x": 90, - "y": 2720, + "y": 2960, "wires": [] }, { @@ -4722,7 +4664,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 12, + "order": 16, "width": 3, "height": 1, "passthru": false, @@ -4734,7 +4676,7 @@ "step": "0.005", "className": "", "x": 450, - "y": 1860, + "y": 2100, "wires": [ [ "11fd3363416433f9" @@ -4749,7 +4691,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 25, + "order": 30, "width": 3, "height": 1, "passthru": false, @@ -4761,7 +4703,7 @@ "step": "0.005", "className": "", "x": 420, - "y": 2100, + "y": 2340, "wires": [ [ "e50492d1e18f43c6" @@ -4776,7 +4718,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 14, + "order": 18, "width": 3, "height": 1, "passthru": false, @@ -4788,7 +4730,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 1900, + "y": 2140, "wires": [ [ "e8b24efb0f30288e" @@ -4803,7 +4745,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 16, + "order": 20, "width": 3, "height": 1, "passthru": false, @@ -4815,7 +4757,7 @@ "step": "100", "className": "", "x": 440, - "y": 1940, + "y": 2180, "wires": [ [ "29f576be9e292232" @@ -4830,7 +4772,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 10, + "order": 14, "width": 3, "height": 1, "passthru": false, @@ -4841,7 +4783,7 @@ "className": "", "topicType": "msg", "x": 460, - "y": 1820, + "y": 2060, "wires": [ [ "78e256083f59f66f" @@ -4853,7 +4795,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 15, + "order": 19, "width": 3, "height": 1, "name": "rotor Accramp", @@ -4862,7 +4804,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 1900, + "y": 2140, "wires": [] }, { @@ -4870,7 +4812,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 9, + "order": 13, "width": 3, "height": 1, "name": "rotor_Steps per Rotation", @@ -4879,7 +4821,7 @@ "layout": "row-spread", "className": "", "x": 810, - "y": 1940, + "y": 2180, "wires": [] }, { @@ -4887,7 +4829,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 13, + "order": 17, "width": 3, "height": 1, "name": "rotor Acc", @@ -4896,7 +4838,7 @@ "layout": "row-left", "className": "", "x": 760, - "y": 1860, + "y": 2100, "wires": [] }, { @@ -4904,7 +4846,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 11, + "order": 15, "width": 3, "height": 1, "name": "rotor_delay", @@ -4913,7 +4855,7 @@ "layout": "row-left", "className": "", "x": 770, - "y": 1820, + "y": 2060, "wires": [] }, { @@ -4921,7 +4863,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 21, + "order": 26, "width": 6, "height": 1, "name": "tt", @@ -4930,7 +4872,7 @@ "layout": "row-center", "className": "", "x": 90, - "y": 2060, + "y": 2300, "wires": [] }, { @@ -4941,7 +4883,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 27, + "order": 32, "width": 3, "height": 1, "passthru": false, @@ -4953,7 +4895,7 @@ "step": "0.1", "className": "", "x": 410, - "y": 2140, + "y": 2380, "wires": [ [ "af88b9da72917d62" @@ -4968,7 +4910,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 29, + "order": 34, "width": 3, "height": 1, "passthru": false, @@ -4980,7 +4922,7 @@ "step": "1", "className": "", "x": 430, - "y": 2180, + "y": 2420, "wires": [ [ "b1b4678827d3a6dd" @@ -4995,7 +4937,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 23, + "order": 28, "width": 3, "height": 1, "passthru": false, @@ -5006,7 +4948,7 @@ "className": "", "topicType": "msg", "x": 450, - "y": 2060, + "y": 2300, "wires": [ [ "eef89545ec0f6aa8" @@ -5018,7 +4960,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 28, + "order": 33, "width": 3, "height": 1, "name": "ttAccramp", @@ -5027,7 +4969,7 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2180, + "y": 2420, "wires": [] }, { @@ -5035,7 +4977,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 22, + "order": 27, "width": 3, "height": 1, "name": "tt_steps per Rotation", @@ -5044,7 +4986,7 @@ "layout": "row-spread", "className": "", "x": 800, - "y": 2060, + "y": 2300, "wires": [] }, { @@ -5052,7 +4994,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 26, + "order": 31, "width": 3, "height": 1, "name": "tt Acc", @@ -5061,7 +5003,7 @@ "layout": "row-left", "className": "", "x": 750, - "y": 2140, + "y": 2380, "wires": [] }, { @@ -5069,7 +5011,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 24, + "order": 29, "width": 3, "height": 1, "name": "tt_delay", @@ -5078,7 +5020,7 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2100, + "y": 2340, "wires": [] }, { @@ -5089,7 +5031,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 18, + "order": 22, "width": 3, "height": 1, "passthru": false, @@ -5101,7 +5043,7 @@ "step": "1", "className": "", "x": 430, - "y": 1980, + "y": 2220, "wires": [ [ "c4b5a38c5c1df3d2" @@ -5113,7 +5055,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 17, + "order": 21, "width": 3, "height": 1, "name": "rotor_angle", @@ -5122,7 +5064,7 @@ "layout": "row-spread", "className": "", "x": 770, - "y": 1980, + "y": 2220, "wires": [] }, { @@ -5133,7 +5075,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 31, + "order": 36, "width": 3, "height": 1, "passthru": false, @@ -5145,7 +5087,7 @@ "step": "1", "className": "", "x": 420, - "y": 2220, + "y": 2460, "wires": [ [ "0f3367983bb8e159" @@ -5157,7 +5099,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 30, + "order": 35, "width": 3, "height": 1, "name": "tt_angle", @@ -5166,7 +5108,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2220, + "y": 2460, "wires": [] }, { @@ -5183,7 +5125,7 @@ "layout": "row-center", "className": "", "x": 90, - "y": 1700, + "y": 1820, "wires": [] }, { @@ -5194,7 +5136,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 33, + "order": 38, "width": 3, "height": 1, "passthru": false, @@ -5206,7 +5148,7 @@ "step": "1", "className": "", "x": 410, - "y": 2260, + "y": 2500, "wires": [ [ "c9d2e31514def4fc" @@ -5221,7 +5163,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 20, + "order": 24, "width": 3, "height": 1, "passthru": false, @@ -5233,7 +5175,7 @@ "step": "1", "className": "", "x": 420, - "y": 2020, + "y": 2260, "wires": [ [ "523717b0f218a5fd" @@ -5245,7 +5187,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 32, + "order": 37, "width": 3, "height": 1, "name": "tt_dir", @@ -5254,7 +5196,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2260, + "y": 2500, "wires": [] }, { @@ -5262,7 +5204,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 19, + "order": 23, "width": 3, "height": 1, "name": "rotor_dir", @@ -5271,7 +5213,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2020, + "y": 2260, "wires": [] }, { @@ -5300,7 +5242,7 @@ "46b91bef44714366" ], "x": 955, - "y": 460, + "y": 480, "wires": [] }, { @@ -5323,7 +5265,7 @@ "step": "0.02", "className": "", "x": 430, - "y": 2360, + "y": 2600, "wires": [ [ "5c752757090c49d2" @@ -5350,7 +5292,7 @@ "step": "0.1", "className": "", "x": 400, - "y": 2400, + "y": 2640, "wires": [ [ "a1769f0277834f6d" @@ -5377,7 +5319,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2520, + "y": 2760, "wires": [ [ "1a8b0ba21b4f3005", @@ -5405,7 +5347,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2560, + "y": 2800, "wires": [ [ "dc8fc962ff7d594b", @@ -5433,7 +5375,7 @@ "step": "1", "className": "", "x": 410, - "y": 2600, + "y": 2840, "wires": [ [ "00e7836ccb3c4d0c" @@ -5454,7 +5396,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2360, + "y": 2600, "wires": [] }, { @@ -5471,7 +5413,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2400, + "y": 2640, "wires": [] }, { @@ -5488,7 +5430,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2520, + "y": 2760, "wires": [] }, { @@ -5505,7 +5447,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2560, + "y": 2800, "wires": [] }, { @@ -5522,7 +5464,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2600, + "y": 2840, "wires": [] }, { @@ -5544,7 +5486,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 2760, + "y": 3000, "wires": [ [ "885bc559fafec5f2" @@ -5565,7 +5507,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 2760, + "y": 3000, "wires": [] }, { @@ -5587,7 +5529,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 2800, + "y": 3040, "wires": [ [ "f70321c96bf81360" @@ -5608,7 +5550,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 2800, + "y": 3040, "wires": [] }, { @@ -5630,7 +5572,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 2840, + "y": 3080, "wires": [ [ "95e1603bbd06a69d" @@ -5651,7 +5593,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 2840, + "y": 3080, "wires": [] }, { @@ -5673,7 +5615,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 2880, + "y": 3120, "wires": [ [ "a8f92ea6bf394640" @@ -5694,7 +5636,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2880, + "y": 3120, "wires": [] }, { @@ -5716,7 +5658,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 2920, + "y": 3160, "wires": [ [ "06397bb46b3bb541" @@ -5737,7 +5679,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2920, + "y": 3160, "wires": [] }, { @@ -5759,7 +5701,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 2960, + "y": 3200, "wires": [ [ "687dcdc1ede11700" @@ -5780,7 +5722,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2960, + "y": 3200, "wires": [] }, { @@ -5802,7 +5744,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3000, + "y": 3240, "wires": [ [ "e220740c0d38ccb0" @@ -5823,7 +5765,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3000, + "y": 3240, "wires": [] }, { @@ -5845,7 +5787,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3040, + "y": 3280, "wires": [ [ "79d7e5a705ab813a" @@ -5866,7 +5808,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3040, + "y": 3280, "wires": [] }, { @@ -5888,7 +5830,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3080, + "y": 3320, "wires": [ [ "12d20f2274bcc511" @@ -5909,7 +5851,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3080, + "y": 3320, "wires": [] }, { @@ -5931,7 +5873,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3120, + "y": 3360, "wires": [ [ "a4a89668ce4c9f05" @@ -5952,7 +5894,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3120, + "y": 3360, "wires": [] }, { @@ -5971,7 +5913,7 @@ "topic": "", "name": "confirm", "x": 680, - "y": 460, + "y": 480, "wires": [ [ "29745a36fc157f3f" @@ -5986,7 +5928,7 @@ "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", "outputs": 1, "x": 530, - "y": 460, + "y": 480, "wires": [ [ "5fcef1cb2e9e4788" @@ -6013,7 +5955,7 @@ "step": "90", "className": "", "x": 410, - "y": 2640, + "y": 2880, "wires": [ [ "3019576de193d9d6" @@ -6034,7 +5976,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2640, + "y": 2880, "wires": [] }, { @@ -6049,7 +5991,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1820, + "y": 2060, "wires": [ [ "dfdebe10dbf0e198" @@ -6068,7 +6010,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1820, + "y": 2060, "wires": [ [] ] @@ -6085,7 +6027,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1940, + "y": 2180, "wires": [ [ "9a56c087d941f1da" @@ -6104,7 +6046,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1940, + "y": 2180, "wires": [ [] ] @@ -6121,7 +6063,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1980, + "y": 2220, "wires": [ [ "0dfc86d90258f9bb" @@ -6140,7 +6082,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1980, + "y": 2220, "wires": [ [] ] @@ -6157,7 +6099,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2020, + "y": 2260, "wires": [ [ "1361134e9847f003" @@ -6176,7 +6118,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2020, + "y": 2260, "wires": [ [] ] @@ -6193,7 +6135,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1860, + "y": 2100, "wires": [ [ "b03e8b51187e88eb" @@ -6212,7 +6154,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1860, + "y": 2100, "wires": [ [] ] @@ -6229,7 +6171,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1900, + "y": 2140, "wires": [ [ "543e1690693acbeb" @@ -6248,7 +6190,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1900, + "y": 2140, "wires": [ [] ] @@ -6260,12 +6202,12 @@ "name": "loadI", "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", "outputs": 1, - "noerr": 0, + "noerr": 4, "initialize": "", "finalize": "", "libs": [], "x": 290, - "y": 2060, + "y": 2300, "wires": [ [ "c6642c7470d3820c" @@ -6284,7 +6226,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2180, + "y": 2420, "wires": [ [ "721b9680a3fa460e" @@ -6303,7 +6245,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2220, + "y": 2460, "wires": [ [ "1610895f430b9aca" @@ -6322,7 +6264,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2260, + "y": 2500, "wires": [ [ "277037c4716d85bf" @@ -6341,7 +6283,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2100, + "y": 2340, "wires": [ [ "6aae9d4fddf08cc0" @@ -6360,7 +6302,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2140, + "y": 2380, "wires": [ [ "10687d331a732790" @@ -6379,7 +6321,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2060, + "y": 2300, "wires": [ [] ] @@ -6396,7 +6338,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2180, + "y": 2420, "wires": [ [] ] @@ -6413,7 +6355,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2220, + "y": 2460, "wires": [ [] ] @@ -6430,7 +6372,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2260, + "y": 2500, "wires": [ [] ] @@ -6447,7 +6389,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2100, + "y": 2340, "wires": [ [] ] @@ -6464,7 +6406,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2140, + "y": 2380, "wires": [ [] ] @@ -6481,7 +6423,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2360, + "y": 2600, "wires": [ [ "2522f888dc58972f" @@ -6500,7 +6442,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2360, + "y": 2600, "wires": [ [] ] @@ -6517,7 +6459,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2400, + "y": 2640, "wires": [ [ "30e8df3d616512d8" @@ -6536,7 +6478,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2400, + "y": 2640, "wires": [ [] ] @@ -6553,7 +6495,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2520, + "y": 2760, "wires": [ [ "d855d926df89d65b" @@ -6572,7 +6514,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2520, + "y": 2760, "wires": [ [] ] @@ -6589,7 +6531,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2560, + "y": 2800, "wires": [ [ "7617517dc8ba2859" @@ -6608,7 +6550,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2560, + "y": 2800, "wires": [ [] ] @@ -6625,7 +6567,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2600, + "y": 2840, "wires": [ [ "cbaa23c34e10fae1" @@ -6644,7 +6586,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2600, + "y": 2840, "wires": [ [] ] @@ -6661,7 +6603,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2640, + "y": 2880, "wires": [ [ "f455fb39039617ae" @@ -6680,7 +6622,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2640, + "y": 2880, "wires": [ [] ] @@ -6697,7 +6639,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 2760, + "y": 3000, "wires": [ [ "e89f61dbe6a6cffe" @@ -6716,7 +6658,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 2760, + "y": 3000, "wires": [ [] ] @@ -6733,7 +6675,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3080, + "y": 3320, "wires": [ [ "eef25405472acfee" @@ -6752,7 +6694,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3080, + "y": 3320, "wires": [ [] ] @@ -6769,7 +6711,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 2800, + "y": 3040, "wires": [ [ "70014da0b6ab6698" @@ -6788,7 +6730,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 2800, + "y": 3040, "wires": [ [] ] @@ -6805,7 +6747,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 2840, + "y": 3080, "wires": [ [ "2544963852c6881a" @@ -6824,7 +6766,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 2840, + "y": 3080, "wires": [ [] ] @@ -6841,7 +6783,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 2880, + "y": 3120, "wires": [ [ "a1394401246eb735" @@ -6860,7 +6802,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 2880, + "y": 3120, "wires": [ [] ] @@ -6877,7 +6819,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 2920, + "y": 3160, "wires": [ [ "f15ca4518b5f223e" @@ -6896,7 +6838,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 2920, + "y": 3160, "wires": [ [] ] @@ -6913,7 +6855,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 2960, + "y": 3200, "wires": [ [ "49900bb9047dd965" @@ -6932,7 +6874,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 2960, + "y": 3200, "wires": [ [] ] @@ -6949,7 +6891,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3000, + "y": 3240, "wires": [ [ "5a90224dc998b417" @@ -6968,7 +6910,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3040, + "y": 3280, "wires": [ [ "d2364ab09627fe94" @@ -6987,7 +6929,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3000, + "y": 3240, "wires": [ [] ] @@ -7004,7 +6946,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3040, + "y": 3280, "wires": [ [] ] @@ -7021,7 +6963,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3120, + "y": 3360, "wires": [ [ "74e455136b5ca5dd" @@ -7040,7 +6982,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3120, + "y": 3360, "wires": [ [] ] @@ -7050,7 +6992,7 @@ "type": "function", "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -7069,7 +7011,7 @@ "type": "function", "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -7095,7 +7037,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 460, + "y": 480, "wires": [ [ "f6d6cc35679ede63" @@ -7169,7 +7111,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1700, + "y": 1820, "wires": [ [ "6ebd15c61a5ca891" @@ -7190,7 +7132,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 1700, + "y": 1820, "wires": [] }, { @@ -7205,7 +7147,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1700, + "y": 1820, "wires": [ [] ] @@ -7230,7 +7172,7 @@ "step": "5", "className": "", "x": 440, - "y": 1700, + "y": 1820, "wires": [ [ "acd10a4c99ee8063" @@ -7257,7 +7199,7 @@ "step": "5", "className": "", "x": 440, - "y": 1740, + "y": 1860, "wires": [ [ "031d7697768d0e77" @@ -7284,7 +7226,7 @@ "step": "5", "className": "", "x": 440, - "y": 1780, + "y": 1900, "wires": [ [ "be1954dd71d2c94c" @@ -7303,7 +7245,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1740, + "y": 1860, "wires": [ [ "3ad0f0f206e4a873" @@ -7322,7 +7264,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1740, + "y": 1860, "wires": [ [] ] @@ -7339,7 +7281,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 1780, + "y": 1900, "wires": [ [ "3b6d759ed5be647f" @@ -7358,7 +7300,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 1780, + "y": 1900, "wires": [ [] ] @@ -7377,7 +7319,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 1740, + "y": 1860, "wires": [] }, { @@ -7394,7 +7336,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 1780, + "y": 1900, "wires": [] }, { @@ -7403,8 +7345,8 @@ "z": "e43a27722b508115", "name": "enable projectname", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "50eeb3e362f9027f", + "960912e90ba5b5bc" ], "x": 135, "y": 360, @@ -7412,7 +7354,8 @@ [ "22ef66b0e2058be2", "9ce01c8ba97932c1", - "81356177176eebcf" + "81356177176eebcf", + "d54b85891248ba88" ] ] }, @@ -7446,7 +7389,7 @@ "50eeb3e362f9027f" ], "x": 185, - "y": 1700, + "y": 1820, "wires": [ [ "0f0871baf322b6d0", @@ -7463,7 +7406,9 @@ "6b2eb1cb95e573f9", "ed4d587cb4feb064", "5b02160c33605ae7", - "304c135ec09801e3" + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" ] ] }, @@ -7473,11 +7418,11 @@ "z": "e43a27722b508115", "name": "enable projectname", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "50eeb3e362f9027f", + "960912e90ba5b5bc" ], "x": 155, - "y": 2300, + "y": 2540, "wires": [ [ "43fe948b3e7234e2", @@ -7500,7 +7445,7 @@ "50eeb3e362f9027f" ], "x": 135, - "y": 2760, + "y": 3000, "wires": [ [ "77bb7dc529d63a7e", @@ -7636,7 +7581,7 @@ "bgcolor": "transparent", "className": "", "icon": "fa-question-circle", - "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", "payloadType": "str", "topic": "topic", "topicType": "msg", @@ -7656,7 +7601,7 @@ "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, "x": 660, - "y": 2480, + "y": 2720, "wires": [ [] ] @@ -7669,7 +7614,7 @@ "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, "x": 660, - "y": 2440, + "y": 2680, "wires": [ [] ] @@ -7694,7 +7639,7 @@ "step": "0.02", "className": "", "x": 440, - "y": 2320, + "y": 2560, "wires": [ [ "e612073aded01a8f" @@ -7715,7 +7660,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2320, + "y": 2560, "wires": [] }, { @@ -7730,7 +7675,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2320, + "y": 2560, "wires": [ [ "81bd4381cd029958" @@ -7749,7 +7694,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2320, + "y": 2560, "wires": [ [] ] @@ -8098,84 +8043,585 @@ "wires": [] }, { - "id": "4c7fa5b5b27b83a5", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'betaArdu'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", - "outputs": 1, - "x": 260, - "y": 140, + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, "wires": [ [ - "e23c514008cad1a1" + "85ad07b8f973bbe2" ] ] }, { - "id": "80175eb8dc6ad009", - "type": "inject", - "z": "a5557543ccff5889", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 100, - "y": 140, - "wires": [ - [ - "4c7fa5b5b27b83a5" - ] - ] + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] }, { - "id": "d7362e6e0ec7bdaa", - "type": "inject", - "z": "a5557543ccff5889", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 90, - "y": 220, + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, "wires": [ - [ - "4ce127c61c3c5966", - "beacc3dc5398fa79" - ] + [] ] }, { - "id": "4ce127c61c3c5966", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "prepare image creation", + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, +{ + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, +{ + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", "outputs": 1, "x": 290, @@ -8219,7 +8665,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -8254,7 +8700,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -8320,7 +8766,7 @@ "options": [ { "label": "stable", - "value": "main", + "value": "stable", "type": "str" }, { @@ -8328,11 +8774,11 @@ "value": "beta", "type": "str" }, - { - "label": "betaArdu", - "value": "betaArdu", + { + "label": "meanwhile", + "value": "meanwhile", "type": "str" - } + } ], "payload": "", "topic": "topic", @@ -8538,7 +8984,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/OpenScanEu/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, @@ -8898,7 +9344,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "Changelog", - "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/OpenScan-org/OpenScan-Doc/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", "outputs": 1, "x": 430, "y": 640, @@ -8929,4 +9375,4 @@ [] ] } -] \ No newline at end of file +] diff --git a/update/main/settings.js b/update/stable/settings.js similarity index 92% rename from update/main/settings.js rename to update/stable/settings.js index 834f457..357b02b 100644 --- a/update/main/settings.js +++ b/update/stable/settings.js @@ -1,5 +1,5 @@ /** - * Node-RED Settings created at Mon, 24 Jan 2022 08:17:31 GMT + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT * * It can contain any valid JavaScript code that will get run when Node-RED * is started. @@ -19,6 +19,7 @@ * - Node Settings * **/ +process.env.HOSTNAME = require('os').hostname(); module.exports = { @@ -54,7 +55,8 @@ module.exports = { * property can be used */ //userDir: '/home/nol/.node-red/', - userDir: '/home/pi/OpenScan/settings/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + /** Node-RED scans the `nodes` directory in the userDir to find local node files. * The following property can be used to specify an additional directory to scan. */ @@ -137,11 +139,12 @@ module.exports = { * - httpNodeCors * - httpNodeMiddleware * - httpStatic + * - httpStaticRoot ******************************************************************************/ /** the tcp port that the Node-RED web server is listening on */ -// uiPort: process.env.PORT || 1880, -uiPort: process.env.PORT || 80, + uiPort: process.env.PORT || 80, + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. * To listen on all IPv6 addresses, set uiHost to "::", * The following property can be used to listen on a specific interface. For @@ -164,8 +167,8 @@ uiPort: process.env.PORT || 80, * The following property can be used to specify a different root path. * If set to false, this is disabled. */ - //httpAdminRoot: '/admin', -httpAdminRoot: '/editor', + httpAdminRoot: '/editor', + /** The following property can be used to add a custom middleware function * in front of all admin http routes. For example, to set custom http * headers. It can be a single function or an array of middleware functions. @@ -218,9 +221,28 @@ httpAdminRoot: '/editor', /** When httpAdminRoot is used to move the UI to a different root path, the * following property can be used to identify a directory of static content * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot */ - //httpStatic: '/home/nol/node-red-static/', -httpStatic: '/home/pi/OpenScan/', + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + /******************************************************************************* * Runtime Settings * - lang @@ -348,9 +370,9 @@ httpStatic: '/home/pi/OpenScan/', }, codeEditor: { /** Select the text editor component used by the editor. - * Defaults to "ace", but can be set to "ace" or "monaco" + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired */ - lib: "ace", + lib: "monaco", options: { /** The follow options only apply if the editor is set to "monaco" * @@ -360,7 +382,7 @@ httpStatic: '/home/pi/OpenScan/', */ theme: "vs", /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. - * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html */ //fontSize: 14, //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", @@ -405,13 +427,14 @@ httpStatic: '/home/pi/OpenScan/', * will allow the `os` module to be accessed in a Function node using: * global.get("os") */ - functionGlobalContext: { - os:require('os'), - path:require('path'), - fs:require('fs'), - -}, - +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, /** The maximum number of messages nodes will buffer internally as part of their * operation. This applies across a range of nodes that operate on message sequences. * defaults to no limit. A value of 0 also means no limit is applied. @@ -425,8 +448,8 @@ httpStatic: '/home/pi/OpenScan/', * middleware:{function or array}, (req,res,next) - http middleware * ioMiddleware:{function or array}, (socket,next) - socket.io middleware */ - //ui: { path: "ui" }, -ui: { path: "" }, + ui: { path: "" }, + /** Colourise the console output of the debug node */ //debugUseColors: true, diff --git a/update/update.json b/update/update.json index 40fcdb3..97c4e76 100644 --- a/update/update.json +++ b/update/update.json @@ -1,140 +1,84 @@ { - "mini": { + "stable": { "1": { - "src": "mini/fla.py", + "src": "stable/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 3910 + "filesize": 13012 }, "2": { - "src": "mini/Arducam.py", - "dst": "/usr/lib/python3/dist-packages/Arducam.py", - "filesize": 6154 - }, - "3": { - "src": "mini/OpenScan.py", + "src": "stable/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 5864 - }, - "4": { - "src": "mini/config.txt", - "dst": "/boot/config.txt", - "filesize": 2196 - }, - "5": { - "src": "mini/flows.json", - "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 178895 - }, - "6": { - "src": "mini/settings.js", - "dst": "/root/.node-red/settings.js", - "filesize": 20321 - }, - "7": { - "src": "files/logo2.jpg", - "dst": "/home/pi/OpenScan/files/logo.jpg", - "filesize": 582519 - } - }, - "main": { - "1": { - "src": "main/fla.py", - "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 5366 - }, - "2": { - "src": "main/Arducam.py", - "dst": "/usr/lib/python3/dist-packages/Arducam.py", - "filesize": 6154 + "filesize": 10253 }, "3": { - "src": "main/OpenScan.py", - "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 6161 + "src": "stable/config.txt", + "dst": "/boot/config.txt", + "filesize": 2185 }, "4": { - "src": "main/config.txt", - "dst": "/boot/config.txt", - "filesize": 2179 + "src": "stable/flows.json.tmpl", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", + "filesize": 325625 }, "5": { - "src": "main/flows.json", - "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 350544 - }, - "6": { - "src": "main/settings.js", + "src": "stable/settings.js", "dst": "/root/.node-red/settings.js", - "filesize": 20321 - }, - "7": { - "src": "files/logo.jpg", - "dst": "/home/pi/OpenScan/files/logo.jpg", - "filesize": 582519 + "filesize": 21248 } }, + "beta": { "1": { "src": "beta/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 5366 + "filesize": 13012 }, "2": { - "src": "beta/Arducam.py", - "dst": "/usr/lib/python3/dist-packages/Arducam.py", - "filesize": 6154 - }, - "3": { "src": "beta/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 6443 + "filesize": 10253 }, - "4": { + "3": { "src": "beta/config.txt", "dst": "/boot/config.txt", - "filesize": 2179 + "filesize": 2185 }, - "5": { - "src": "beta/flows.json", - "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 353390 + "4": { + "src": "beta/flows.json.tmpl", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", + "filesize": 337413 }, - "6": { + "5": { "src": "beta/settings.js", "dst": "/root/.node-red/settings.js", - "filesize": 20321 - }, - "7": { - "src": "files/logo.jpg", - "dst": "/home/pi/OpenScan/files/logo.jpg", - "filesize": 582519 + "filesize": 21248 } }, - "betaArdu": { + "meanwhile": { "1": { - "src": "betaArdu/fla.py", + "src": "meanwhile/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 12089 + "filesize": 13012 }, "2": { - "src": "betaArdu/OpenScan.py", + "src": "meanwhile/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10060 + "filesize": 10253 }, "3": { - "src": "betaArdu/config.txt", + "src": "meanwhile/config.txt", "dst": "/boot/config.txt", "filesize": 2185 }, "4": { - "src": "betaArdu/flows.json", - "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 310044 + "src": "meanwhile/flows.json.tmpl", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", + "filesize": 337413 }, "5": { - "src": "betaArdu/settings.js", + "src": "meanwhile/settings.js", "dst": "/root/.node-red/settings.js", - "filesize": 21199 + "filesize": 21248 } } } \ No newline at end of file From ceff2e799868f7365c22956eba0bf10a877ccfec Mon Sep 17 00:00:00 2001 From: Unus-Multorum Date: Thu, 27 Jun 2024 20:56:34 +0200 Subject: [PATCH 02/38] Add support for motor endstops (#1) * Add support for motor endstops Rotor endstop works and can be configured in settings * Update flows.json.tmpl fixed flows.json.tmpl to keep variables instead of value --- update/meanwhile/OpenScan.py | 10 +- update/meanwhile/flows.json.tmpl | 356 +++++++++++++++++++------------ update/update.json | 4 +- 3 files changed, 231 insertions(+), 139 deletions(-) diff --git a/update/meanwhile/OpenScan.py b/update/meanwhile/OpenScan.py index 681c78d..9cc5e77 100644 --- a/update/meanwhile/OpenScan.py +++ b/update/meanwhile/OpenScan.py @@ -133,7 +133,7 @@ def camera(cmd, msg = {}): except: return 400 -def motorrun(motor,angle,ES_enable=False,ES_start_state = True): +def motorrun(motor,angle,ES_enable=False): #motor can be "rotor", "tt" or "extra" import RPi.GPIO as GPIO from time import sleep @@ -147,6 +147,7 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): dirpin = load_int('pin_' + motor + '_dir') steppin = load_int('pin_' + motor +'_step') ES_pin = load_int('pin_' + motor + '_endstop') + ES_pushed = load_bool(motor + '_endstop_pushed') dir = load_int(motor + '_dir') ramp = load_int(motor + '_accramp') acc = load_float(motor + '_acc') @@ -163,11 +164,12 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): if(step_count<0): GPIO.output(dirpin, GPIO.LOW) step_count=-step_count + for x in range(step_count): - if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + if ES_enable == True and GPIO.input(ES_pin) == ES_pushed and (motor == "rotor" and GPIO.input(dirpin) == False): i = 0 while i <= 10: - if GPIO.input(ES_pin) == ES_start_state: + if GPIO.input(ES_pin) != ES_pushed: i = 11 if i == 10: return @@ -231,7 +233,7 @@ def take_photo(file): else: cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus - + system(cmd) return cmd diff --git a/update/meanwhile/flows.json.tmpl b/update/meanwhile/flows.json.tmpl index 20fb35f..81f4f3c 100644 --- a/update/meanwhile/flows.json.tmpl +++ b/update/meanwhile/flows.json.tmpl @@ -87,7 +87,7 @@ }, "themeState": { "base-color": { - "default": "{{ base-color-default }}", + "default": "{{ base-color-default }}", "value": "{{ base-color-value }}", "edited": false }, @@ -484,7 +484,7 @@ "collapse": false, "className": "" }, -{ + { "id": "15edc2ce885dddb3", "type": "ui_group", "name": "Colorines", @@ -495,7 +495,7 @@ "collapse": false, "className": "" }, -{ + { "id": "33aff36289823faa", "type": "ui_group", "name": "Monitoring", @@ -1663,7 +1663,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "motorrun", - "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "func": "from OpenScan import motorrun, load_int, load_bool\n\nif 'payload' not in msg:\n return\nenable_endstop = load_bool('rotor_enable_endstop')\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'), enable_endstop)\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'), enable_endstop)\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'), enable_endstop)\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'), enable_endstop)", "outputs": 1, "x": 780, "y": 840, @@ -2164,7 +2164,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nenable_endstop = load_bool('rotor_enable_endstop')\n\nif enable_endstop:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nautofocus = load_bool('cam_autofocus') ##change##\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\n##change##\nif focus_min == focus_max or autofocus:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef focus(f):\n ##change##\n if autofocus:\n camera('/picam2_af')\n else:\n camera('/picam2_focus?focus=' + str(f))\n ##change##\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n ##change##\n focus(focuslist[returning[0]])\n if returning[0] < len(focuslist) - 1:\n returning[0] += 1\n else:\n returning[0] = 0\n ##change##\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\n\ndef stack_photo(i):\n\n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n\ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n ##change##\n focus(focuslist[i+1])\n else:\n camera(focuslist[0])\n sleep(1.7)\n\ndef photo_stack():\n camera(focuslist[0])\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n\n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n\n focus_thread.start()\n photo_thread.start()\n\n focus_thread.join()\n photo_thread.join()\n\n\ndef move_motor(enable_endstop=False):\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle,enable_endstop)\n motorrun('rotor',rotor_angle,enable_endstop)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor(enable_endstop)\n sleep(load_float(\"cam_delay_before\"))\n\n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -4698,7 +4698,7 @@ "step": "0.005", "className": "", "x": 450, - "y": 2100, + "y": 2140, "wires": [ [ "11fd3363416433f9" @@ -4725,7 +4725,7 @@ "step": "0.005", "className": "", "x": 420, - "y": 2340, + "y": 2380, "wires": [ [ "e50492d1e18f43c6" @@ -4752,7 +4752,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2140, + "y": 2180, "wires": [ [ "e8b24efb0f30288e" @@ -4779,7 +4779,7 @@ "step": "100", "className": "", "x": 440, - "y": 2180, + "y": 2220, "wires": [ [ "29f576be9e292232" @@ -4805,7 +4805,7 @@ "className": "", "topicType": "msg", "x": 460, - "y": 2060, + "y": 2100, "wires": [ [ "78e256083f59f66f" @@ -4826,7 +4826,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 2140, + "y": 2180, "wires": [] }, { @@ -4843,7 +4843,7 @@ "layout": "row-spread", "className": "", "x": 810, - "y": 2180, + "y": 2220, "wires": [] }, { @@ -4860,7 +4860,7 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2100, + "y": 2140, "wires": [] }, { @@ -4877,7 +4877,7 @@ "layout": "row-left", "className": "", "x": 770, - "y": 2060, + "y": 2100, "wires": [] }, { @@ -4917,7 +4917,7 @@ "step": "0.1", "className": "", "x": 410, - "y": 2380, + "y": 2420, "wires": [ [ "af88b9da72917d62" @@ -4944,7 +4944,7 @@ "step": "1", "className": "", "x": 430, - "y": 2420, + "y": 2460, "wires": [ [ "b1b4678827d3a6dd" @@ -4970,7 +4970,7 @@ "className": "", "topicType": "msg", "x": 450, - "y": 2300, + "y": 2340, "wires": [ [ "eef89545ec0f6aa8" @@ -4991,7 +4991,7 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2420, + "y": 2460, "wires": [] }, { @@ -5008,7 +5008,7 @@ "layout": "row-spread", "className": "", "x": 800, - "y": 2300, + "y": 2340, "wires": [] }, { @@ -5025,7 +5025,7 @@ "layout": "row-left", "className": "", "x": 750, - "y": 2380, + "y": 2420, "wires": [] }, { @@ -5042,7 +5042,7 @@ "layout": "row-left", "className": "", "x": 760, - "y": 2340, + "y": 2380, "wires": [] }, { @@ -5065,7 +5065,7 @@ "step": "1", "className": "", "x": 430, - "y": 2220, + "y": 2260, "wires": [ [ "c4b5a38c5c1df3d2" @@ -5086,7 +5086,7 @@ "layout": "row-spread", "className": "", "x": 770, - "y": 2220, + "y": 2260, "wires": [] }, { @@ -5109,7 +5109,7 @@ "step": "1", "className": "", "x": 420, - "y": 2460, + "y": 2500, "wires": [ [ "0f3367983bb8e159" @@ -5130,7 +5130,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2460, + "y": 2500, "wires": [] }, { @@ -5170,7 +5170,7 @@ "step": "1", "className": "", "x": 410, - "y": 2500, + "y": 2540, "wires": [ [ "c9d2e31514def4fc" @@ -5197,7 +5197,7 @@ "step": "1", "className": "", "x": 420, - "y": 2260, + "y": 2300, "wires": [ [ "523717b0f218a5fd" @@ -5218,7 +5218,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2500, + "y": 2540, "wires": [] }, { @@ -5235,7 +5235,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2260, + "y": 2300, "wires": [] }, { @@ -5287,7 +5287,7 @@ "step": "0.02", "className": "", "x": 430, - "y": 2600, + "y": 2680, "wires": [ [ "5c752757090c49d2" @@ -5314,7 +5314,7 @@ "step": "0.1", "className": "", "x": 400, - "y": 2640, + "y": 2720, "wires": [ [ "a1769f0277834f6d" @@ -5341,7 +5341,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2760, + "y": 2840, "wires": [ [ "1a8b0ba21b4f3005", @@ -5369,7 +5369,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2800, + "y": 2880, "wires": [ [ "dc8fc962ff7d594b", @@ -5397,7 +5397,7 @@ "step": "1", "className": "", "x": 410, - "y": 2840, + "y": 2920, "wires": [ [ "00e7836ccb3c4d0c" @@ -5418,7 +5418,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2600, + "y": 2680, "wires": [] }, { @@ -5435,7 +5435,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2640, + "y": 2720, "wires": [] }, { @@ -5452,7 +5452,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2760, + "y": 2840, "wires": [] }, { @@ -5469,7 +5469,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2800, + "y": 2880, "wires": [] }, { @@ -5486,7 +5486,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2840, + "y": 2920, "wires": [] }, { @@ -5508,7 +5508,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3000, + "y": 3080, "wires": [ [ "885bc559fafec5f2" @@ -5529,7 +5529,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3000, + "y": 3080, "wires": [] }, { @@ -5551,7 +5551,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3040, + "y": 3120, "wires": [ [ "f70321c96bf81360" @@ -5572,7 +5572,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3040, + "y": 3120, "wires": [] }, { @@ -5594,7 +5594,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3080, + "y": 3160, "wires": [ [ "95e1603bbd06a69d" @@ -5615,7 +5615,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3080, + "y": 3160, "wires": [] }, { @@ -5637,7 +5637,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3120, + "y": 3200, "wires": [ [ "a8f92ea6bf394640" @@ -5658,7 +5658,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3120, + "y": 3200, "wires": [] }, { @@ -5680,7 +5680,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3160, + "y": 3240, "wires": [ [ "06397bb46b3bb541" @@ -5701,7 +5701,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3160, + "y": 3240, "wires": [] }, { @@ -5723,7 +5723,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3200, + "y": 3280, "wires": [ [ "687dcdc1ede11700" @@ -5744,7 +5744,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3200, + "y": 3280, "wires": [] }, { @@ -5766,7 +5766,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3240, + "y": 3320, "wires": [ [ "e220740c0d38ccb0" @@ -5787,7 +5787,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3240, + "y": 3320, "wires": [] }, { @@ -5809,7 +5809,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3280, + "y": 3360, "wires": [ [ "79d7e5a705ab813a" @@ -5830,7 +5830,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3280, + "y": 3360, "wires": [] }, { @@ -5852,7 +5852,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3320, + "y": 3400, "wires": [ [ "12d20f2274bcc511" @@ -5873,7 +5873,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3320, + "y": 3400, "wires": [] }, { @@ -5895,7 +5895,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3360, + "y": 3440, "wires": [ [ "a4a89668ce4c9f05" @@ -5916,7 +5916,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3360, + "y": 3440, "wires": [] }, { @@ -5977,7 +5977,7 @@ "step": "90", "className": "", "x": 410, - "y": 2880, + "y": 2960, "wires": [ [ "3019576de193d9d6" @@ -5998,7 +5998,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2880, + "y": 2960, "wires": [] }, { @@ -6013,7 +6013,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2060, + "y": 2100, "wires": [ [ "dfdebe10dbf0e198" @@ -6032,7 +6032,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2060, + "y": 2100, "wires": [ [] ] @@ -6049,7 +6049,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2180, + "y": 2220, "wires": [ [ "9a56c087d941f1da" @@ -6068,7 +6068,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2180, + "y": 2220, "wires": [ [] ] @@ -6085,7 +6085,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2220, + "y": 2260, "wires": [ [ "0dfc86d90258f9bb" @@ -6104,7 +6104,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2220, + "y": 2260, "wires": [ [] ] @@ -6121,7 +6121,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2260, + "y": 2300, "wires": [ [ "1361134e9847f003" @@ -6140,7 +6140,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2260, + "y": 2300, "wires": [ [] ] @@ -6157,7 +6157,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2100, + "y": 2140, "wires": [ [ "b03e8b51187e88eb" @@ -6176,7 +6176,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2100, + "y": 2140, "wires": [ [] ] @@ -6193,7 +6193,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2140, + "y": 2180, "wires": [ [ "543e1690693acbeb" @@ -6212,7 +6212,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2140, + "y": 2180, "wires": [ [] ] @@ -6229,7 +6229,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2300, + "y": 2340, "wires": [ [ "c6642c7470d3820c" @@ -6248,7 +6248,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2420, + "y": 2460, "wires": [ [ "721b9680a3fa460e" @@ -6267,7 +6267,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2460, + "y": 2500, "wires": [ [ "1610895f430b9aca" @@ -6286,7 +6286,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2500, + "y": 2540, "wires": [ [ "277037c4716d85bf" @@ -6305,7 +6305,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2340, + "y": 2380, "wires": [ [ "6aae9d4fddf08cc0" @@ -6324,7 +6324,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2380, + "y": 2420, "wires": [ [ "10687d331a732790" @@ -6343,7 +6343,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2300, + "y": 2340, "wires": [ [] ] @@ -6360,7 +6360,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2420, + "y": 2460, "wires": [ [] ] @@ -6377,7 +6377,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2460, + "y": 2500, "wires": [ [] ] @@ -6394,7 +6394,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2500, + "y": 2540, "wires": [ [] ] @@ -6411,7 +6411,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2340, + "y": 2380, "wires": [ [] ] @@ -6428,7 +6428,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2380, + "y": 2420, "wires": [ [] ] @@ -6445,7 +6445,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2600, + "y": 2680, "wires": [ [ "2522f888dc58972f" @@ -6464,7 +6464,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2600, + "y": 2680, "wires": [ [] ] @@ -6481,7 +6481,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2640, + "y": 2720, "wires": [ [ "30e8df3d616512d8" @@ -6500,7 +6500,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2640, + "y": 2720, "wires": [ [] ] @@ -6517,7 +6517,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2760, + "y": 2840, "wires": [ [ "d855d926df89d65b" @@ -6536,7 +6536,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2760, + "y": 2840, "wires": [ [] ] @@ -6553,7 +6553,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2800, + "y": 2880, "wires": [ [ "7617517dc8ba2859" @@ -6572,7 +6572,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2800, + "y": 2880, "wires": [ [] ] @@ -6589,7 +6589,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2840, + "y": 2920, "wires": [ [ "cbaa23c34e10fae1" @@ -6608,7 +6608,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2840, + "y": 2920, "wires": [ [] ] @@ -6625,7 +6625,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2880, + "y": 2960, "wires": [ [ "f455fb39039617ae" @@ -6644,7 +6644,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2880, + "y": 2960, "wires": [ [] ] @@ -6661,7 +6661,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3000, + "y": 3080, "wires": [ [ "e89f61dbe6a6cffe" @@ -6680,7 +6680,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3000, + "y": 3080, "wires": [ [] ] @@ -6697,7 +6697,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3320, + "y": 3400, "wires": [ [ "eef25405472acfee" @@ -6716,7 +6716,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3320, + "y": 3400, "wires": [ [] ] @@ -6733,7 +6733,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3040, + "y": 3120, "wires": [ [ "70014da0b6ab6698" @@ -6752,7 +6752,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3040, + "y": 3120, "wires": [ [] ] @@ -6769,7 +6769,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3080, + "y": 3160, "wires": [ [ "2544963852c6881a" @@ -6788,7 +6788,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3080, + "y": 3160, "wires": [ [] ] @@ -6805,7 +6805,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3120, + "y": 3200, "wires": [ [ "a1394401246eb735" @@ -6824,7 +6824,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3120, + "y": 3200, "wires": [ [] ] @@ -6841,7 +6841,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3160, + "y": 3240, "wires": [ [ "f15ca4518b5f223e" @@ -6860,7 +6860,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3160, + "y": 3240, "wires": [ [] ] @@ -6877,7 +6877,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3200, + "y": 3280, "wires": [ [ "49900bb9047dd965" @@ -6896,7 +6896,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3200, + "y": 3280, "wires": [ [] ] @@ -6913,7 +6913,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3240, + "y": 3320, "wires": [ [ "5a90224dc998b417" @@ -6932,7 +6932,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3280, + "y": 3360, "wires": [ [ "d2364ab09627fe94" @@ -6951,7 +6951,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3240, + "y": 3320, "wires": [ [] ] @@ -6968,7 +6968,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3280, + "y": 3360, "wires": [ [] ] @@ -6985,7 +6985,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3360, + "y": 3440, "wires": [ [ "74e455136b5ca5dd" @@ -7004,7 +7004,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3360, + "y": 3440, "wires": [ [] ] @@ -7430,7 +7430,8 @@ "5b02160c33605ae7", "304c135ec09801e3", "f036424d79645761", - "b7db72b7f0599ebd" + "b7db72b7f0599ebd", + "fe62e12d458db2d4" ] ] }, @@ -7623,7 +7624,7 @@ "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, "x": 660, - "y": 2720, + "y": 2800, "wires": [ [] ] @@ -7636,7 +7637,7 @@ "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, "x": 660, - "y": 2680, + "y": 2760, "wires": [ [] ] @@ -7661,7 +7662,7 @@ "step": "0.02", "className": "", "x": 440, - "y": 2560, + "y": 2640, "wires": [ [ "e612073aded01a8f" @@ -7682,7 +7683,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2560, + "y": 2640, "wires": [] }, { @@ -7697,7 +7698,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2560, + "y": 2640, "wires": [ [ "81bd4381cd029958" @@ -7716,7 +7717,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2560, + "y": 2640, "wires": [ [] ] @@ -8248,7 +8249,9 @@ "wires": [ [ "69516440e3997111", - "f036424d79645761" + "f036424d79645761", + "fe62e12d458db2d4", + "aea4e51b20951560" ] ] }, @@ -8518,7 +8521,7 @@ [] ] }, -{ + { "id": "c59e7b205d80fe0a", "type": "ui_button", "z": "e43a27722b508115", @@ -8546,7 +8549,7 @@ ] ] }, -{ + { "id": "2afb6a45c73fa244", "type": "link in", "z": "e43a27722b508115", @@ -8565,7 +8568,7 @@ ] ] }, -{ + { "id": "69885a9ce218eb71", "type": "comment", "z": "e43a27722b508115", @@ -8681,7 +8684,7 @@ ] ] }, -{ + { "id": "667950f6671bd1a0", "type": "function", "z": "e43a27722b508115", @@ -8743,7 +8746,7 @@ ] ] }, -{ + { "id": "5fd155711e29b1b8", "type": "comment", "z": "e43a27722b508115", @@ -8899,7 +8902,7 @@ ] ] }, -{ + { "id": "a1b81e7fe94ad4e5", "type": "python3-function", "z": "e43a27722b508115", @@ -8940,6 +8943,93 @@ ] ] }, + { + "id": "fe62e12d458db2d4", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = false;\n}\nelse{\n data = true;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "96e2b45a7102156b" + ] + ] + }, + { + "id": "96e2b45a7102156b", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_endstop_pushed", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "false", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "true", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 2060, + "wires": [ + [ + "df66caa5e0497e65" + ] + ] + }, + { + "id": "df66caa5e0497e65", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "aea4e51b20951560", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_endstop_pushed", + "label": "Reverse Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 2060, + "wires": [] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", @@ -9171,11 +9261,11 @@ "value": "beta", "type": "str" }, - { + { "label": "meanwhile", "value": "meanwhile", "type": "str" - } + } ], "payload": "", "topic": "topic", diff --git a/update/update.json b/update/update.json index 97c4e76..22e0132 100644 --- a/update/update.json +++ b/update/update.json @@ -63,7 +63,7 @@ "2": { "src": "meanwhile/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10253 + "filesize": 10322 }, "3": { "src": "meanwhile/config.txt", @@ -73,7 +73,7 @@ "4": { "src": "meanwhile/flows.json.tmpl", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", - "filesize": 337413 + "filesize": 340275 }, "5": { "src": "meanwhile/settings.js", From f5e3562a3309a2de94b1aaa150f7df42b23b2824 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 15 Sep 2024 17:03:08 +0200 Subject: [PATCH 03/38] add swagger --- update/2024-11S/beta/OpenScan.py | 316 + update/2024-11S/beta/config.txt | 85 + update/2024-11S/beta/fla.py | 351 + update/2024-11S/beta/flows.json | 9852 ++++++++++++++++++++ update/2024-11S/beta/flows.json.tmpl | 9852 ++++++++++++++++++++ update/2024-11S/beta/settings.js | 512 ++ update/2024-11S/meanwhile/OpenScan.py | 316 + update/2024-11S/meanwhile/config.txt | 85 + update/2024-11S/meanwhile/fla.py | 394 + update/2024-11S/meanwhile/flows.json | 9459 ++++++++++++++++++++ update/2024-11S/meanwhile/flows.json.tmpl | 9906 +++++++++++++++++++++ update/2024-11S/meanwhile/routine.py | 240 + update/2024-11S/meanwhile/settings.js | 512 ++ update/2024-11S/meanwhile/start.sh | 11 + update/2024-11S/stable/OpenScan.py | 316 + update/2024-11S/stable/config.txt | 85 + update/2024-11S/stable/fla.py | 394 + update/2024-11S/stable/flows.json.tmpl | 9378 +++++++++++++++++++ update/2024-11S/stable/settings.js | 512 ++ update/2024-11S/update.json | 84 + 20 files changed, 52660 insertions(+) create mode 100644 update/2024-11S/beta/OpenScan.py create mode 100755 update/2024-11S/beta/config.txt create mode 100644 update/2024-11S/beta/fla.py create mode 100644 update/2024-11S/beta/flows.json create mode 100644 update/2024-11S/beta/flows.json.tmpl create mode 100644 update/2024-11S/beta/settings.js create mode 100644 update/2024-11S/meanwhile/OpenScan.py create mode 100755 update/2024-11S/meanwhile/config.txt create mode 100644 update/2024-11S/meanwhile/fla.py create mode 100644 update/2024-11S/meanwhile/flows.json create mode 100644 update/2024-11S/meanwhile/flows.json.tmpl create mode 100644 update/2024-11S/meanwhile/routine.py create mode 100644 update/2024-11S/meanwhile/settings.js create mode 100755 update/2024-11S/meanwhile/start.sh create mode 100644 update/2024-11S/stable/OpenScan.py create mode 100755 update/2024-11S/stable/config.txt create mode 100644 update/2024-11S/stable/fla.py create mode 100644 update/2024-11S/stable/flows.json.tmpl create mode 100644 update/2024-11S/stable/settings.js create mode 100644 update/2024-11S/update.json diff --git a/update/2024-11S/beta/OpenScan.py b/update/2024-11S/beta/OpenScan.py new file mode 100644 index 0000000..681c78d --- /dev/null +++ b/update/2024-11S/beta/OpenScan.py @@ -0,0 +1,316 @@ +basepath = '/home/pi/OpenScan/' +from os.path import isfile +import os + +def load_bool(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + if value == '1' or value == 'True' or value =='true': + value = True + else: + value = False + return value + +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + + return True + + +def load_str(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + return value + +def load_int(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = int(file.read().replace('\n','')) + return value + +def load_float(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = float(file.read().replace('\n','')) + return value + +def save(name, value): + filename = basepath+'settings/'+name + with open(filename, 'w+') as file: + file.write(str(value)) + return + +def OpenScanCloud(cmd, msg): + from requests import get + osc_user = 'openscan' + osc_pw = 'free' + osc_server = 'http://openscanfeedback.dnsuser.de:1334/' + + try: + r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) + except: + r = type('obj', (object,), {'status_code' : 404, 'text':None}) + return r + +def camera(cmd, msg = {}): + from requests import get + flask = 'http://127.0.0.1:1312/' + try: + r = get(flask + cmd, params=msg) + return r.status_code + except: + return 400 + +def motorrun(motor,angle,ES_enable=False,ES_start_state = True): + #motor can be "rotor", "tt" or "extra" + import RPi.GPIO as GPIO + from time import sleep + from math import cos + msg = {'cmd':'set'} + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + + spr = load_int(motor + '_stepsperrotation') + dirpin = load_int('pin_' + motor + '_dir') + steppin = load_int('pin_' + motor +'_step') + ES_pin = load_int('pin_' + motor + '_endstop') + dir = load_int(motor + '_dir') + ramp = load_int(motor + '_accramp') + acc = load_float(motor + '_acc') + delay_init = load_float(motor + '_delay') + delay = delay_init + + step_count=int(angle*spr/360) * dir + GPIO.setup(dirpin, GPIO.OUT) + GPIO.setup(steppin, GPIO.OUT) + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + + if (step_count>0): + GPIO.output(dirpin, GPIO.HIGH) + if(step_count<0): + GPIO.output(dirpin, GPIO.LOW) + step_count=-step_count + for x in range(step_count): + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + + GPIO.output(steppin, GPIO.HIGH) + if x<=ramp and x<=step_count/2: + delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) + #delay=delay_init+(ramp-x)*(delay_init)/acc + elif step_count-x<=ramp and x>step_count/2: + delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) + #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc + else: + delay = delay_init + sleep(delay) + GPIO.output(steppin, GPIO.LOW) + sleep(delay) + +def ringlight(number,state): + import RPi.GPIO as GPIO + msg = {'cmd':'set'} + pin = load_int('pin_ringlight' + str(number)) + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(pin, GPIO.OUT) + GPIO.output(pin, state) + +def take_photo(file): + from os import system + filepath = basepath + file + + model=load_str('model') + + + + shutter = str(load_int('cam_shutter')) + saturation = load_str('cam_saturation') + contrast = load_str('cam_contrast') + awbg_red = load_str('cam_awbg_red') + awbg_blue = load_str('cam_awbg_blue') + gain = load_str('cam_gain') + quality = load_int('cam_jpeg_quality') + filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' + #width = load_str('cam_resx') + #height = load_str('cam_resy') + timeout = load_str('cam_timeout') + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + AF = load_bool('cam_AFmode') + camera = load_str('camera') + + + if camera == 'imx519' and AF == True: + autofocus = ' --autofocus ' + else: + autofocus = '' + + if camera == "usb_webcam": + cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 + else: + cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' + # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + + system(cmd) + return cmd + +def get_points(samples=1): + from math import pi, sqrt, acos, atan2, cos, sin + + points = [] + phi = pi * (3. - sqrt(5.)) + for i in range(int(samples)): + y = 1 - (i / float(samples - 1)) * 2 + radius = sqrt(1 - y * y) + theta = phi * i + x = cos(theta) * radius + z = sin(theta) * radius + r=sqrt(x*x+y*y+z*z) + theta_neu=acos(z/r)*180/pi + phi_neu=atan2(y,x)*180/pi + points.append((theta_neu-90,phi_neu)) + points.sort() + return points + +def create_coordinates(angle_min, angle_max,point_count): + point_count_final=point_count + if angle_max < angle_min: + a = angle_min + angle_min = angle_max + angle_max = a + point_count=point_count*90/(angle_max-angle_min) + actual_points=0 + while actual_pointsangle_min and x20: + point_count=point_count+3 + else: + point_count=point_count+1 + return filtered + + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/2024-11S/beta/config.txt b/update/2024-11S/beta/config.txt new file mode 100755 index 0000000..cc525ae --- /dev/null +++ b/update/2024-11S/beta/config.txt @@ -0,0 +1,85 @@ +# For more options and information see +# http://rpf.io/configtxt +# Some settings may impact device functionality. See link above for details + +# uncomment if you get no picture on HDMI for a default "safe" mode +#hdmi_safe=1 + +# uncomment the following to adjust overscan. Use positive numbers if console +# goes off screen, and negative if there is too much border +#overscan_left=16 +#overscan_right=16 +#overscan_top=16 +#overscan_bottom=16 + +# uncomment to force a console size. By default it will be display's size minus +# overscan. +#framebuffer_width=1280 +#framebuffer_height=720 + +# uncomment if hdmi display is not detected and composite is being output +#hdmi_force_hotplug=1 + +# uncomment to force a specific HDMI mode (this will force VGA) +#hdmi_group=1 +#hdmi_mode=1 + +# uncomment to force a HDMI mode rather than DVI. This can make audio work in +# DMT (computer monitor) modes +#hdmi_drive=2 + +# uncomment to increase signal to HDMI, if you have interference, blanking, or +# no display +#config_hdmi_boost=4 + +# uncomment for composite PAL +#sdtv_mode=2 + +#uncomment to overclock the arm. 700 MHz is the default. +#arm_freq=800 + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Uncomment this to enable infrared communication. +#dtoverlay=gpio-ir,gpio_pin=17 +#dtoverlay=gpio-ir-tx,gpio_pin=18 + +# Additional overlays and parameters are documented /boot/overlays/README + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Disable compensation for displays with overscan +disable_overscan=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] + +[pi4] +# Run as fast as firmware / board allows +arm_boost=1 + +[all] +camera_auto_detect=0 +gpu_mem=256 +dtoverlay=vc4-fkms-v3d +dtoverlay=imx519 +#dtoverlay=imx519,media-controller=1 diff --git a/update/2024-11S/beta/fla.py b/update/2024-11S/beta/fla.py new file mode 100644 index 0000000..57f4660 --- /dev/null +++ b/update/2024-11S/beta/fla.py @@ -0,0 +1,351 @@ +from flask import Flask, make_response, jsonify, request, abort, redirect +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont +from time import sleep, time +import shutil +from OpenScan import load_int, load_float, load_bool, ringlight +import RPi.GPIO as GPIO +from math import sqrt +import os +import math +from skimage import io, feature, color, transform +import numpy as np +from scipy import ndimage +import socket + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +app = Flask(__name__) + +basedir = '/home/pi/OpenScan/' +timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + + +################################################################################################################### +@app.route('/shutdown', methods=['get']) +def shutdown(): + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + os.system('shutdown -h now') + + else: + return redirect("http://" + hostname, code=302) +################################################################################################################### +@app.route('/reboot', methods=['get']) +def reboot(): + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + f = open("/home/pi/OpenScan/settings/session_token", "r") + session_token = (f.readline())[:20] + if shutdown_token == session_token: + delay = 0.1 + ringlight(2,False) + + for i in range (5): + ringlight(1,True) + sleep(delay) + ringlight(1,False) + sleep(delay) + + os.system('reboot -h') + else: + return redirect("http://" + hostname, code=302) +################################################################################################################### + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) + + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') + + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wrong aspect ratio! +# preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}) + preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}, controls ={"FrameDurationLimits": (1, 1000000)}) + +# preview_config = picam2.create_preview_configuration(main={"size": (2328, 1748)}) + capture_config = picam2.create_still_configuration(controls ={"FrameDurationLimits": (1, 1000000)}) + picam2.configure(preview_config) + picam2.controls.AnalogueGain = 1.0 + picam2.start() + return ({}, 200) + +################################################################################################################### +@app.route('/picam2_take_photo', methods=['get']) +def picam2_take_photo(): + starttime = time() + + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + img = picam2.capture_image() + + if cam_mode !=1: + img = img.convert('RGB') + w,h = img.size + + if cropx != 0 or cropy != 0: + img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) + + if rotation == 90: + img = img.transpose(Image.ROTATE_90) + elif rotation == 180: + img= img.transpose(Image.ROTATE_180) + elif rotation == 270: + img= img.transpose(Image.ROTATE_270) + + if load_bool("cam_mask"): + if cam_mode == 1: + downscale = 0.045*1.4 + else: + downscale = 0.1*1.4 + img = create_mask(img, downscale) + + if load_bool("cam_features") and not load_bool("cam_sharparea"): + img = plot_orb_keypoints(img) + + if load_bool("cam_sharparea") and not load_bool("cam_features"): + img2 = highlight_sharpest_areas(img) + img = overlay_mask(img, img2) + + if cam_mode != 1 and not load_bool("cam_sharparea") and not load_bool("cam_features"): + img = add_histo(img) + + img.save("/home/pi/OpenScan/tmp2/preview.jpg", quality=load_int('cam_jpeg_quality')) + print("total " + str(int(1000*(time()-starttime))) + "ms") + starttime = time() + + return ({}, 200) +################################################################################################################### +@app.route('/picam2_focus', methods=['get']) +def picam2_focus(): + focus = float(request.args.get('focus')) + picam2.set_controls({"AfMode": 0, "LensPosition": focus}) + return ({}, 200) +################################################################################################################### +@app.route('/picam2_af1', methods=['get']) +def picam2_af1(): + from libcamera import controls + + picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0, "AfRange":controls.AfRangeEnum.Macro}) + return ({}, 200) +################################################################################################################### +@app.route('/picam2_af2', methods=['get']) +def picam2_af2(): + picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0}) + return ({}, 200) + +################################################################################################################### +@app.route('/picam2_exposure', methods=['get']) +def picam2_exposure(): + exposure = int(request.args.get('exposure')) + picam2.controls.AnalogueGain = 1.0 + picam2.controls.ExposureTime = exposure + return ({}, 200) +################################################################################################################### +@app.route('/picam2_contrast', methods=['get']) +def picam2_contrast(): + contrast = float(request.args.get('contrast')) + picam2.controls.Contrast = contrast + return ({}, 200) +################################################################################################################### +@app.route('/picam2_saturation', methods=['get']) +def picam2_saturation(): + saturation = float(request.args.get('saturation')) + picam2.controls.Saturation = saturation + return ({}, 200) +################################################################################################################### +@app.route('/picam2_switch_mode', methods=['get']) +def picam2_switch_mode(): + global cam_mode + cam_mode = int(request.args.get('mode')) + if cam_mode == 1: + picam2.switch_mode(capture_config) + else: + picam2.switch_mode(preview_config) + return ({}, 200) +################################################################################################################### +@app.route('/picam2_show_mode', methods=['get']) +def picam2_show_mode(): + global cam_mode + return({"mode":cam_mode},200) +################################################################################################################### +@app.route('/picam2_af', methods=['get']) +def picam2_af(): + picam2.set_controls({"AfMode": 1 ,"AfTrigger": 0}) # --> wait 3-5s + return ({}, 200) + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + +if __name__ == '__main__': +# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) + app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/2024-11S/beta/flows.json b/update/2024-11S/beta/flows.json new file mode 100644 index 0000000..c003c13 --- /dev/null +++ b/update/2024-11S/beta/flows.json @@ -0,0 +1,9852 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "#097479", + "value": "#097479", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#097479", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "#097479", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "15edc2ce885dddb3", + "type": "ui_group", + "name": "Colorines", + "tab": "457102eadc9ddb6c", + "order": 8, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "33aff36289823faa", + "type": "ui_group", + "name": "Monitoring", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 460, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 380, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 380, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nmsg['inactivity'] = False\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 720, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 720, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1 and msg['inactivity'] is False:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd", + "5a8dac2ff3dfaaa3" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 525, + "y": 1040, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "var inactivity = msg['inactivity']\n\nif (inactivity) {\n global.set('light', 0);\n msg.light = 0;\n\n\n} else{\n global.set('light', 1);\n msg.light = 1;\n}\n\nmsg['inactivity'] = false\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad", + "8a4d7b329733f52b" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, + { + "id": "5a8dac2ff3dfaaa3", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 880, + "y": 960, + "wires": [] + }, + { + "id": "8a4d7b329733f52b", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 400, + "y": 920, + "wires": [] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\nmsg['hostname']=host\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2100, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2340, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2140, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2180, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2060, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2140, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2180, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2100, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2060, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2380, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2420, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2300, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2420, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2300, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2380, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2340, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2220, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2220, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2460, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2500, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2260, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2500, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2260, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2600, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2640, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2760, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2800, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2600, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2640, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2760, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2800, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2840, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3000, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3000, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3040, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3040, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3120, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3120, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3160, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3160, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3240, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3240, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3280, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3280, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3320, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3320, + "wires": [] + }, + { + "id": "74e455136b5ca5dd", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 21, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3360, + "wires": [ + [ + "a4a89668ce4c9f05" + ] + ] + }, + { + "id": "3a74f653800eb831", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, + "width": 4, + "height": 1, + "name": "endstop2", + "label": "Endstop Turntable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3360, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2880, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2600, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2600, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2760, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2800, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3000, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3000, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3040, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3040, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "21dc963d967d9c99", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3360, + "wires": [ + [ + "74e455136b5ca5dd" + ] + ] + }, + { + "id": "a4a89668ce4c9f05", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3360, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2560, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2560, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, + { + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "69885a9ce218eb71", + "type": "comment", + "z": "e43a27722b508115", + "name": "Coloritos", + "info": "", + "x": 100, + "y": 3740, + "wires": [] + }, + { + "id": "dc1cde67c3022e6b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3800, + "wires": [ + [ + "0dccca85770c7936" + ] + ] + }, + { + "id": "b63e8246ad14ad9d", + "type": "function", + "z": "e43a27722b508115", + "name": "interface-color", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 540, + "y": 3800, + "wires": [ + [] + ] + }, + { + "id": "b7044aa75196b521", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3800, + "wires": [ + [ + "dc1cde67c3022e6b" + ] + ] + }, + { + "id": "0dccca85770c7936", + "type": "ui_dropdown", + "z": "e43a27722b508115", + "name": "interface_color", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "Aburrido", + "value": "#097479", + "type": "str" + }, + { + "label": "Morado", + "value": "#790974", + "type": "str" + }, + { + "label": "Berenjena", + "value": "#79093c", + "type": "str" + }, + { + "label": "Azul", + "value": "#093c79 ", + "type": "str" + }, + { + "label": "Oliva", + "value": "#747909", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 360, + "y": 3800, + "wires": [ + [ + "b63e8246ad14ad9d" + ] + ] + }, + { + "id": "667950f6671bd1a0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 840, + "wires": [ + [ + "b82a1cbefad51cd8" + ] + ] + }, + { + "id": "5f32d7e78e368454", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "b82a1cbefad51cd8", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "hostname", + "label": "Hostname", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "mode": "text", + "delay": 300, + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 360, + "y": 840, + "wires": [ + [ + "5f32d7e78e368454" + ] + ] + }, + { + "id": "5fd155711e29b1b8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Monitoring", + "info": "", + "x": 100, + "y": 3860, + "wires": [] + }, + { + "id": "815702499384f118", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3920, + "wires": [ + [ + "bfdbdae28bf42ed4" + ] + ] + }, + { + "id": "464c8495f86daaa7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "datadog_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3920, + "wires": [ + [] + ] + }, + { + "id": "bfdbdae28bf42ed4", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "datadog_enable", + "label": "Enable Datadog", + "tooltip": "Enable Datadog monitoring", + "group": "33aff36289823faa", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3920, + "wires": [ + [ + "464c8495f86daaa7" + ] + ] + }, + { + "id": "f93ce2d26953341f", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "label": "Datadog Api Token", + "tooltip": "Datadog Api Token", + "group": "33aff36289823faa", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3960, + "wires": [ + [ + "647641e79884eb87" + ] + ] + }, + { + "id": "ee668e39d213070b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3960, + "wires": [ + [ + "f93ce2d26953341f" + ] + ] + }, + { + "id": "647641e79884eb87", + "type": "function", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3960, + "wires": [ + [] + ] + }, + { + "id": "ff2dea1ab9cb7776", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 4", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3960, + "wires": [ + [ + "815702499384f118", + "ee668e39d213070b" + ] + ] + }, + { + "id": "a1b81e7fe94ad4e5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", + "outputs": 1, + "x": 530, + "y": 3740, + "wires": [ + [] + ] + }, + { + "id": "2f3a3c0e682ae862", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "restart_interface", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "Restart Interface", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 340, + "y": 3740, + "wires": [ + [ + "a1b81e7fe94ad4e5" + ] + ] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 10, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "cde61b7de9eeaba7", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "3ce32450.e0cffc", + "order": 9, + "width": 0, + "height": 0, + "passthru": false, + "label": "Expand Root", + "tooltip": "Sets the maximum space your SD card admits", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "expand", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 510, + "y": 1020, + "wires": [ + [ + "eab36487d201f867" + ] + ] + }, + { + "id": "eab36487d201f867", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "", + "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", + "outputs": 1, + "x": 690, + "y": 1020, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/update/2024-11S/beta/flows.json.tmpl b/update/2024-11S/beta/flows.json.tmpl new file mode 100644 index 0000000..32e12fe --- /dev/null +++ b/update/2024-11S/beta/flows.json.tmpl @@ -0,0 +1,9852 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "{{ darktheme-default }}", + "baseColor": "{{ darktheme-basecolor }}", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "{{ base-color-default }}", + "value": "{{ base-color-value }}", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "{{ page-titlebar-bgcolor }}", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "{{ widget-bgcolor }}", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, +{ + "id": "15edc2ce885dddb3", + "type": "ui_group", + "name": "Colorines", + "tab": "457102eadc9ddb6c", + "order": 8, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, +{ + "id": "33aff36289823faa", + "type": "ui_group", + "name": "Monitoring", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 460, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 380, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 380, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nmsg['inactivity'] = False\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 720, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 720, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1 and msg['inactivity'] is False:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd", + "5a8dac2ff3dfaaa3" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 525, + "y": 1040, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "var inactivity = msg['inactivity']\n\nif (inactivity) {\n global.set('light', 0);\n msg.light = 0;\n\n\n} else{\n global.set('light', 1);\n msg.light = 1;\n}\n\nmsg['inactivity'] = false\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad", + "8a4d7b329733f52b" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, +{ + "id": "5a8dac2ff3dfaaa3", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 880, + "y": 960, + "wires": [] + }, + { + "id": "8a4d7b329733f52b", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 400, + "y": 920, + "wires": [] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\nmsg['hostname']=host\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2100, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2340, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2140, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2180, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2060, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2140, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2180, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2100, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2060, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2380, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2420, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2300, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2420, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2300, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2380, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2340, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2220, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2220, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2460, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2500, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2260, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2500, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2260, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2600, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2640, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2760, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2800, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2600, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2640, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2760, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2800, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2840, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3000, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3000, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3040, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3040, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3120, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3120, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3160, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3160, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3240, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3240, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3280, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3280, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3320, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3320, + "wires": [] + }, + { + "id": "74e455136b5ca5dd", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 21, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3360, + "wires": [ + [ + "a4a89668ce4c9f05" + ] + ] + }, + { + "id": "3a74f653800eb831", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, + "width": 4, + "height": 1, + "name": "endstop2", + "label": "Endstop Turntable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3360, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2880, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2600, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2600, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2760, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2800, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3000, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3000, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3040, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3040, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "21dc963d967d9c99", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3360, + "wires": [ + [ + "74e455136b5ca5dd" + ] + ] + }, + { + "id": "a4a89668ce4c9f05", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3360, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2560, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2560, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, +{ + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, +{ + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, +{ + "id": "69885a9ce218eb71", + "type": "comment", + "z": "e43a27722b508115", + "name": "Coloritos", + "info": "", + "x": 100, + "y": 3740, + "wires": [] + }, + { + "id": "dc1cde67c3022e6b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3800, + "wires": [ + [ + "0dccca85770c7936" + ] + ] + }, + { + "id": "b63e8246ad14ad9d", + "type": "function", + "z": "e43a27722b508115", + "name": "interface-color", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 540, + "y": 3800, + "wires": [ + [] + ] + }, + { + "id": "b7044aa75196b521", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3800, + "wires": [ + [ + "dc1cde67c3022e6b" + ] + ] + }, + { + "id": "0dccca85770c7936", + "type": "ui_dropdown", + "z": "e43a27722b508115", + "name": "interface_color", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "Aburrido", + "value": "#097479", + "type": "str" + }, + { + "label": "Morado", + "value": "#790974", + "type": "str" + }, + { + "label": "Berenjena", + "value": "#79093c", + "type": "str" + }, + { + "label": "Azul", + "value": "#093c79 ", + "type": "str" + }, + { + "label": "Oliva", + "value": "#747909", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 360, + "y": 3800, + "wires": [ + [ + "b63e8246ad14ad9d" + ] + ] + }, +{ + "id": "667950f6671bd1a0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 840, + "wires": [ + [ + "b82a1cbefad51cd8" + ] + ] + }, + { + "id": "5f32d7e78e368454", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "b82a1cbefad51cd8", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "hostname", + "label": "Hostname", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "mode": "text", + "delay": 300, + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 360, + "y": 840, + "wires": [ + [ + "5f32d7e78e368454" + ] + ] + }, +{ + "id": "5fd155711e29b1b8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Monitoring", + "info": "", + "x": 100, + "y": 3860, + "wires": [] + }, + { + "id": "815702499384f118", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3920, + "wires": [ + [ + "bfdbdae28bf42ed4" + ] + ] + }, + { + "id": "464c8495f86daaa7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "datadog_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3920, + "wires": [ + [] + ] + }, + { + "id": "bfdbdae28bf42ed4", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "datadog_enable", + "label": "Enable Datadog", + "tooltip": "Enable Datadog monitoring", + "group": "33aff36289823faa", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3920, + "wires": [ + [ + "464c8495f86daaa7" + ] + ] + }, + { + "id": "f93ce2d26953341f", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "label": "Datadog Api Token", + "tooltip": "Datadog Api Token", + "group": "33aff36289823faa", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3960, + "wires": [ + [ + "647641e79884eb87" + ] + ] + }, + { + "id": "ee668e39d213070b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3960, + "wires": [ + [ + "f93ce2d26953341f" + ] + ] + }, + { + "id": "647641e79884eb87", + "type": "function", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3960, + "wires": [ + [] + ] + }, + { + "id": "ff2dea1ab9cb7776", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 4", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3960, + "wires": [ + [ + "815702499384f118", + "ee668e39d213070b" + ] + ] + }, +{ + "id": "a1b81e7fe94ad4e5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", + "outputs": 1, + "x": 530, + "y": 3740, + "wires": [ + [] + ] + }, + { + "id": "2f3a3c0e682ae862", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "restart_interface", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "Restart Interface", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 340, + "y": 3740, + "wires": [ + [ + "a1b81e7fe94ad4e5" + ] + ] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/202411S/update/202411S/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 10, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "cde61b7de9eeaba7", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "3ce32450.e0cffc", + "order": 9, + "width": 0, + "height": 0, + "passthru": false, + "label": "Expand Root", + "tooltip": "Sets the maximum space your SD card admits", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "expand", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 510, + "y": 1020, + "wires": [ + [ + "eab36487d201f867" + ] + ] + }, + { + "id": "eab36487d201f867", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "", + "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", + "outputs": 1, + "x": 690, + "y": 1020, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/update/2024-11S/beta/settings.js b/update/2024-11S/beta/settings.js new file mode 100644 index 0000000..357b02b --- /dev/null +++ b/update/2024-11S/beta/settings.js @@ -0,0 +1,512 @@ +/** + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT + * + * It can contain any valid JavaScript code that will get run when Node-RED + * is started. + * + * Lines that start with // are commented out. + * Each entry should be separated from the entries above and below by a comma ',' + * + * For more information about individual settings, refer to the documentation: + * https://nodered.org/docs/user-guide/runtime/configuration + * + * The settings are split into the following sections: + * - Flow File and User Directory Settings + * - Security + * - Server Settings + * - Runtime Settings + * - Editor Settings + * - Node Settings + * + **/ +process.env.HOSTNAME = require('os').hostname(); + +module.exports = { + +/******************************************************************************* + * Flow File and User Directory Settings + * - flowFile + * - credentialSecret + * - flowFilePretty + * - userDir + * - nodesDir + ******************************************************************************/ + + /** The file containing the flows. If not set, defaults to flows_.json **/ + flowFile: "flows.json", + + /** By default, credentials are encrypted in storage using a generated key. To + * specify your own secret, set the following property. + * If you want to disable encryption of credentials, set this property to false. + * Note: once you set this property, do not change it - doing so will prevent + * node-red from being able to decrypt your existing credentials and they will be + * lost. + */ + credentialSecret: false, + + /** By default, the flow JSON will be formatted over multiple lines making + * it easier to compare changes when using version control. + * To disable pretty-printing of the JSON set the following property to false. + */ + flowFilePretty: true, + + /** By default, all user data is stored in a directory called `.node-red` under + * the user's home directory. To use a different location, the following + * property can be used + */ + //userDir: '/home/nol/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + + /** Node-RED scans the `nodes` directory in the userDir to find local node files. + * The following property can be used to specify an additional directory to scan. + */ + //nodesDir: '/home/nol/.node-red/nodes', + +/******************************************************************************* + * Security + * - adminAuth + * - https + * - httpsRefreshInterval + * - requireHttps + * - httpNodeAuth + * - httpStaticAuth + ******************************************************************************/ + + /** To password protect the Node-RED editor and admin API, the following + * property can be used. See http://nodered.org/docs/security.html for details. + */ + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + /** The following property can be used to enable HTTPS + * This property can be either an object, containing both a (private) key + * and a (public) certificate, or a function that returns such an object. + * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + * for details of its contents. + */ + + /** Option 1: static object */ + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + + /** Option 2: function that returns the HTTP configuration object */ + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + /** If the `https` setting is a function, the following setting can be used + * to set how often, in hours, the function will be called. That can be used + * to refresh any certificates. + */ + //httpsRefreshInterval : 12, + + /** The following property can be used to cause insecure HTTP connections to + * be redirected to HTTPS. + */ + //requireHttps: true, + + /** To password protect the node-defined HTTP endpoints (httpNodeRoot), + * including node-red-dashboard, or the static content (httpStatic), the + * following properties can be used. + * The `pass` field is a bcrypt hash of the password. + * See http://nodered.org/docs/security.html#generating-the-password-hash + */ + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + +/******************************************************************************* + * Server Settings + * - uiPort + * - uiHost + * - apiMaxLength + * - httpServerOptions + * - httpAdminRoot + * - httpAdminMiddleware + * - httpNodeRoot + * - httpNodeCors + * - httpNodeMiddleware + * - httpStatic + * - httpStaticRoot + ******************************************************************************/ + + /** the tcp port that the Node-RED web server is listening on */ + uiPort: process.env.PORT || 80, + + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. + * To listen on all IPv6 addresses, set uiHost to "::", + * The following property can be used to listen on a specific interface. For + * example, the following would only allow connections from the local machine. + */ + //uiHost: "127.0.0.1", + + /** The maximum size of HTTP request that will be accepted by the runtime api. + * Default: 5mb + */ + //apiMaxLength: '5mb', + + /** The following property can be used to pass custom options to the Express.js + * server used by Node-RED. For a full list of available options, refer + * to http://expressjs.com/en/api.html#app.settings.table + */ + //httpServerOptions: { }, + + /** By default, the Node-RED UI is available at http://localhost:1880/ + * The following property can be used to specify a different root path. + * If set to false, this is disabled. + */ + httpAdminRoot: '/editor', + + /** The following property can be used to add a custom middleware function + * in front of all admin http routes. For example, to set custom http + * headers. It can be a single function or an array of middleware functions. + */ + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + + /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. + * By default, these are served relative to '/'. The following property + * can be used to specifiy a different root path. If set to false, this is + * disabled. + */ + //httpNodeRoot: '/red-nodes', + + /** The following property can be used to configure cross-origin resource sharing + * in the HTTP nodes. + * See https://github.com/troygoode/node-cors#configuration-options for + * details on its contents. The following is a basic permissive set of options: + */ + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + /** If you need to set an http proxy please set an environment variable + * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + * For example - http_proxy=http://myproxy.com:8080 + * (Setting it here will have no effect) + * You may also specify no_proxy (or NO_PROXY) to supply a comma separated + * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + */ + + /** The following property can be used to add a custom middleware function + * in front of all http in nodes. This allows custom authentication to be + * applied to all http in nodes, or any other sort of common request processing. + * It can be a single function or an array of middleware functions. + */ + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + /** When httpAdminRoot is used to move the UI to a different root path, the + * following property can be used to identify a directory of static content + * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot + */ + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + +/******************************************************************************* + * Runtime Settings + * - lang + * - logging + * - contextStorage + * - exportGlobalContextKeys + * - externalModules + ******************************************************************************/ + + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", + + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, + + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, + + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: [], + // denyList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, + + +/******************************************************************************* + * Editor Settings + * - disableEditor + * - editorTheme + ******************************************************************************/ + + /** The following property can be used to disable the editor. The admin API + * is not affected by this option. To disable both the editor and the admin + * API, use either the httpRoot or httpAdminRoot properties + */ + //disableEditor: false, + + /** Customising the editor + * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes + * for all available options. + */ + editorTheme: { + /** The following property can be used to set a custom theme for the editor. + * See https://github.com/node-red-contrib-themes/theme-collection for + * a collection of themes to chose from. + */ + //theme: "", + palette: { + /** The following property can be used to order the categories in the editor + * palette. If a node's category is not in the list, the category will get + * added to the end of the palette. + * If not set, the following default order is used: + */ + //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + }, + projects: { + /** To enable the Projects feature, set this value to true */ + enabled: false, + workflow: { + /** Set the default projects workflow mode. + * - manual - you must manually commit changes + * - auto - changes are automatically committed + * This can be overridden per-user from the 'Git config' + * section of 'User Settings' within the editor + */ + mode: "manual" + } + }, + codeEditor: { + /** Select the text editor component used by the editor. + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired + */ + lib: "monaco", + options: { + /** The follow options only apply if the editor is set to "monaco" + * + * theme - must match the file name of a theme in + * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme + * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" + */ + theme: "vs", + /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html + */ + //fontSize: 14, + //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", + //fontLigatures: true, + } + } + }, + +/******************************************************************************* + * Node Settings + * - fileWorkingDirectory + * - functionGlobalContext + * - functionExternalModules + * - nodeMessageBufferMaxLength + * - ui (for use with Node-RED Dashboard) + * - debugUseColors + * - debugMaxLength + * - execMaxBufferSize + * - httpRequestTimeout + * - mqttReconnectTime + * - serialReconnectTime + * - socketReconnectTime + * - socketTimeout + * - tcpMsgQueueSize + * - inboundWebSocketTimeout + * - tlsConfigDisableLocalFiles + * - webSocketNodeVerifyClient + ******************************************************************************/ + + /** The working directory to handle relative file paths from within the File nodes + * defaults to the working directory of the Node-RED process. + */ + //fileWorkingDirectory: "", + + /** Allow the Function node to load additional npm modules directly */ + functionExternalModules: true, + + /** The following property can be used to set predefined values in Global Context. + * This allows extra node modules to be made available with in Function node. + * For example, the following: + * functionGlobalContext: { os:require('os') } + * will allow the `os` module to be accessed in a Function node using: + * global.get("os") + */ +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, + /** The maximum number of messages nodes will buffer internally as part of their + * operation. This applies across a range of nodes that operate on message sequences. + * defaults to no limit. A value of 0 also means no limit is applied. + */ + //nodeMessageBufferMaxLength: 0, + + /** If you installed the optional node-red-dashboard you can set it's path + * relative to httpNodeRoot + * Other optional properties include + * readOnly:{boolean}, + * middleware:{function or array}, (req,res,next) - http middleware + * ioMiddleware:{function or array}, (socket,next) - socket.io middleware + */ + ui: { path: "" }, + + /** Colourise the console output of the debug node */ + //debugUseColors: true, + + /** The maximum length, in characters, of any message sent to the debug sidebar tab */ + debugMaxLength: 1000, + + /** Maximum buffer size for the exec node. Defaults to 10Mb */ + //execMaxBufferSize: 10000000, + + /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ + //httpRequestTimeout: 120000, + + /** Retry time in milliseconds for MQTT connections */ + mqttReconnectTime: 15000, + + /** Retry time in milliseconds for Serial port connections */ + serialReconnectTime: 15000, + + /** Retry time in milliseconds for TCP socket connections */ + //socketReconnectTime: 10000, + + /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ + //socketTimeout: 120000, + + /** Maximum number of messages to wait in queue while attempting to connect to TCP socket + * defaults to 1000 + */ + //tcpMsgQueueSize: 2000, + + /** Timeout in milliseconds for inbound WebSocket connections that do not + * match any configured node. Defaults to 5000 + */ + //inboundWebSocketTimeout: 5000, + + /** To disable the option for using local files for storing keys and + * certificates in the TLS configuration node, set this to true. + */ + //tlsConfigDisableLocalFiles: true, + + /** The following property can be used to verify websocket connection attempts. + * This allows, for example, the HTTP request headers to be checked to ensure + * they include valid authentication information. + */ + //webSocketNodeVerifyClient: function(info) { + // /** 'info' has three properties: + // * - origin : the value in the Origin header + // * - req : the HTTP request + // * - secure : true if req.connection.authorized or req.connection.encrypted is set + // * + // * The function should return true if the connection should be accepted, false otherwise. + // * + // * Alternatively, if this function is defined to accept a second argument, callback, + // * it can be used to verify the client asynchronously. + // * The callback takes three arguments: + // * - result : boolean, whether to accept the connection or not + // * - code : if result is false, the HTTP error status to return + // * - reason: if result is false, the HTTP reason string to return + // */ + //}, +} diff --git a/update/2024-11S/meanwhile/OpenScan.py b/update/2024-11S/meanwhile/OpenScan.py new file mode 100644 index 0000000..f6e3616 --- /dev/null +++ b/update/2024-11S/meanwhile/OpenScan.py @@ -0,0 +1,316 @@ +basepath = '/home/pi/OpenScan/' +from os.path import isfile +import os + +def load_bool(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + if value == '1' or value == 'True' or value =='true': + value = True + else: + value = False + return value + +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + + return True + + +def load_str(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + return value + +def load_int(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = int(file.read().replace('\n','')) + return value + +def load_float(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = float(file.read().replace('\n','')) + return value + +def save(name, value): + filename = basepath+'settings/'+name + with open(filename, 'w+') as file: + file.write(str(value)) + return + +def OpenScanCloud(cmd, msg): + from requests import get + osc_user = 'openscan' + osc_pw = 'free' + osc_server = 'http://openscanfeedback.dnsuser.de:1334/' + + try: + r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) + except: + r = type('obj', (object,), {'status_code' : 404, 'text':None}) + return r + +def camera(cmd, msg = {}): + from requests import get + flask = 'http://127.0.0.1:1312/' + try: + r = get(flask + cmd, params=msg) + return r.status_code + except: + return 400 + +def motorrun(motor,angle,ES_enable=False): + #motor can be "rotor", "tt" or "extra" + import RPi.GPIO as GPIO + from time import sleep + from math import cos + msg = {'cmd':'set'} + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + + spr = load_int(motor + '_stepsperrotation') + dirpin = load_int('pin_' + motor + '_dir') + steppin = load_int('pin_' + motor +'_step') + ES_pin = load_int('pin_' + motor + '_endstop') + ES_pushed = load_bool(motor + '_endstop_pushed') + dir = load_int(motor + '_dir') + ramp = load_int(motor + '_accramp') + acc = load_float(motor + '_acc') + delay_init = load_float(motor + '_delay') + delay = delay_init + + step_count=int(angle*spr/360) * dir + GPIO.setup(dirpin, GPIO.OUT) + GPIO.setup(steppin, GPIO.OUT) + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + + if (step_count>0): + GPIO.output(dirpin, GPIO.HIGH) + if(step_count<0): + GPIO.output(dirpin, GPIO.LOW) + step_count=-step_count + + for x in range(step_count): + if ES_enable == True and GPIO.input(ES_pin) == ES_pushed and (motor == "rotor" and GPIO.input(dirpin) == False): + i = 0 + while i <= 10: + if GPIO.input(ES_pin) != ES_pushed: + i = 11 + if i == 10: + return + i = i + 1 + + GPIO.output(steppin, GPIO.HIGH) + if x<=ramp and x<=step_count/2: + delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) + #delay=delay_init+(ramp-x)*(delay_init)/acc + elif step_count-x<=ramp and x>step_count/2: + delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) + #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc + else: + delay = delay_init + sleep(delay) + GPIO.output(steppin, GPIO.LOW) + sleep(delay) + +def ringlight(number,state): + import RPi.GPIO as GPIO + msg = {'cmd':'set'} + pin = load_int('pin_ringlight' + str(number)) + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(pin, GPIO.OUT) + GPIO.output(pin, state) + +def take_photo(file): + from os import system + filepath = basepath + file + + model=load_str('model') + + shutter = str(load_int('cam_shutter')) + saturation = load_str('cam_saturation') + contrast = load_str('cam_contrast') + awbg_red = load_str('cam_awbg_red') + awbg_blue = load_str('cam_awbg_blue') + gain = load_str('cam_gain') + quality = load_int('cam_jpeg_quality') + filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' + #width = load_str('cam_resx') + #height = load_str('cam_resy') + timeout = load_str('cam_timeout') + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + AF = load_bool('cam_AFmode') + camera = load_str('camera') + + + if camera == 'imx519' and AF == True: + autofocus = ' --autofocus ' + else: + autofocus = '' + + if camera == "usb_webcam": + cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 + else: + cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' + # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + + system(cmd) + return cmd + +def get_points(samples=1): + from math import pi, sqrt, acos, atan2, cos, sin + + points = [] + phi = pi * (3. - sqrt(5.)) + for i in range(int(samples)): + y = 1 - (i / float(samples - 1)) * 2 + radius = sqrt(1 - y * y) + theta = phi * i + x = cos(theta) * radius + z = sin(theta) * radius + r=sqrt(x*x+y*y+z*z) + theta_neu=acos(z/r)*180/pi + phi_neu=atan2(y,x)*180/pi + points.append((theta_neu-90,phi_neu)) + points.sort() + return points + +def create_coordinates(angle_min, angle_max,point_count): + point_count_final=point_count + if angle_max < angle_min: + a = angle_min + angle_min = angle_max + angle_max = a + point_count=point_count*90/(angle_max-angle_min) + actual_points=0 + while actual_pointsangle_min and x20: + point_count=point_count+3 + else: + point_count=point_count+1 + return filtered + + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/2024-11S/meanwhile/config.txt b/update/2024-11S/meanwhile/config.txt new file mode 100755 index 0000000..cc525ae --- /dev/null +++ b/update/2024-11S/meanwhile/config.txt @@ -0,0 +1,85 @@ +# For more options and information see +# http://rpf.io/configtxt +# Some settings may impact device functionality. See link above for details + +# uncomment if you get no picture on HDMI for a default "safe" mode +#hdmi_safe=1 + +# uncomment the following to adjust overscan. Use positive numbers if console +# goes off screen, and negative if there is too much border +#overscan_left=16 +#overscan_right=16 +#overscan_top=16 +#overscan_bottom=16 + +# uncomment to force a console size. By default it will be display's size minus +# overscan. +#framebuffer_width=1280 +#framebuffer_height=720 + +# uncomment if hdmi display is not detected and composite is being output +#hdmi_force_hotplug=1 + +# uncomment to force a specific HDMI mode (this will force VGA) +#hdmi_group=1 +#hdmi_mode=1 + +# uncomment to force a HDMI mode rather than DVI. This can make audio work in +# DMT (computer monitor) modes +#hdmi_drive=2 + +# uncomment to increase signal to HDMI, if you have interference, blanking, or +# no display +#config_hdmi_boost=4 + +# uncomment for composite PAL +#sdtv_mode=2 + +#uncomment to overclock the arm. 700 MHz is the default. +#arm_freq=800 + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Uncomment this to enable infrared communication. +#dtoverlay=gpio-ir,gpio_pin=17 +#dtoverlay=gpio-ir-tx,gpio_pin=18 + +# Additional overlays and parameters are documented /boot/overlays/README + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Disable compensation for displays with overscan +disable_overscan=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] + +[pi4] +# Run as fast as firmware / board allows +arm_boost=1 + +[all] +camera_auto_detect=0 +gpu_mem=256 +dtoverlay=vc4-fkms-v3d +dtoverlay=imx519 +#dtoverlay=imx519,media-controller=1 diff --git a/update/2024-11S/meanwhile/fla.py b/update/2024-11S/meanwhile/fla.py new file mode 100644 index 0000000..8f273cf --- /dev/null +++ b/update/2024-11S/meanwhile/fla.py @@ -0,0 +1,394 @@ +from flask import Flask, make_response, jsonify, request, abort, redirect +from flask_restx import Resource, Api, Namespace +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont +from time import sleep, time +import shutil +from OpenScan import load_int, load_float, load_bool, ringlight +import RPi.GPIO as GPIO +from math import sqrt +import os +import math +from skimage import io, feature, color, transform +import numpy as np +from scipy import ndimage +import socket + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +app = Flask(__name__) +api = Api(app) + +# Create a namespace for system operations +system_ns = Namespace('system', description='System operations') +camera_ns = Namespace('camera', description='Camera operations') +api.add_namespace(system_ns) +api.add_namespace(camera_ns) + +basedir = '/home/pi/OpenScan/' +timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + + +################################################################################################################### + + + +@system_ns.route('/shutdown') +class Shutdown(Resource): + @system_ns.doc(params={'token': 'Shutdown token for authentication'}) + def get(self): + '''Shutdown the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('shutdown -h now') + return {'message': 'Shutting down'}, 200 + else: + return redirect("http://" + hostname, code=302) + +################################################################################################################### + +@system_ns.route('/reboot') +class Reboot(Resource): + @system_ns.doc(params={'token': 'Reboot token for authentication'}) + def get(self): + '''Reboot the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('reboot -h') + return {'message': 'Rebooting'}, 200 + else: + return redirect("http://" + hostname, code=302) +################################################################################################################### + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) + + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') + + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wait 3-5s + return {'message': 'Auto focus triggered'}, 200 + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + +if __name__ == '__main__': +# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) + app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/2024-11S/meanwhile/flows.json b/update/2024-11S/meanwhile/flows.json new file mode 100644 index 0000000..7365dbf --- /dev/null +++ b/update/2024-11S/meanwhile/flows.json @@ -0,0 +1,9459 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "#097479", + "value": "#097479", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#097479", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "#097479", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 460, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 380, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 380, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 720, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 720, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 525, + "y": 1040, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 130, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 230, + "y": 700, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, + { + "id": "c4031271b9506117", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "5", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 240, + "wires": [ + [ + "b68ec31cd97a982a", + "122f3a4f33a3d30c" + ] + ] + }, + { + "id": "b68ec31cd97a982a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "current_time", + "func": "var file = 'current_time'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});\n\nglobal.set(\"current_time\", msg.payload)", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 870, + "y": 220, + "wires": [ + [ + "98fda7a4f7276ad0" + ] + ] + }, + { + "id": "122f3a4f33a3d30c", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "get_inactivity", + "func": "from OpenScan import save\nimport subprocess\nresult = subprocess.run([\"date\",\"+%sN\",\"-r\",\"/tmp/activity\\|cut\",\"-b1-13\"],capture_output=True, text=True).stdout.strip()\nsave(\"inactivity_check\",result)\nreturn result", + "outputs": 1, + "x": 870, + "y": 260, + "wires": [ + [ + "98fda7a4f7276ad0" + ] + ] + }, + { + "id": "98fda7a4f7276ad0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "set_inactivity_counter", + "func": "var file = 'inactivity_check'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath + file, 'utf8');\nglobal.set('inactivity_check', data)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1080, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2100, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2340, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2140, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2180, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2060, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2140, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2180, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2100, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2060, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2380, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2420, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2300, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2420, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2300, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2380, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2340, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2220, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2220, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2460, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2500, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2260, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2500, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2260, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2600, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2640, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2760, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2800, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2600, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2640, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2760, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2800, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2840, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3000, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3000, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3040, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3040, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3120, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3120, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3160, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3160, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3240, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3240, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3280, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3280, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3320, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3320, + "wires": [] + }, + { + "id": "74e455136b5ca5dd", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 21, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3360, + "wires": [ + [ + "a4a89668ce4c9f05" + ] + ] + }, + { + "id": "3a74f653800eb831", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, + "width": 4, + "height": 1, + "name": "endstop2", + "label": "Endstop Turntable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3360, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2880, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2600, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2600, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2760, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2800, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3000, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3000, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3040, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3040, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "21dc963d967d9c99", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3360, + "wires": [ + [ + "74e455136b5ca5dd" + ] + ] + }, + { + "id": "a4a89668ce4c9f05", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3360, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2560, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2560, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, + { + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 9, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/update/2024-11S/meanwhile/flows.json.tmpl b/update/2024-11S/meanwhile/flows.json.tmpl new file mode 100644 index 0000000..c19a157 --- /dev/null +++ b/update/2024-11S/meanwhile/flows.json.tmpl @@ -0,0 +1,9906 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "{{ darktheme-default }}", + "baseColor": "{{ darktheme-basecolor }}", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "{{ base-color-default }}", + "value": "{{ base-color-value }}", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "{{ page-titlebar-bgcolor }}", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "{{ widget-bgcolor }}", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "15edc2ce885dddb3", + "type": "ui_group", + "name": "Colorines", + "tab": "457102eadc9ddb6c", + "order": 8, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "33aff36289823faa", + "type": "ui_group", + "name": "Monitoring", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 460, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 380, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 380, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 720, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 720, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/camera/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int, load_bool\n\nif 'payload' not in msg:\n return\nenable_endstop = load_bool('rotor_enable_endstop')\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'), enable_endstop)\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'), enable_endstop)\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'), enable_endstop)\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'), enable_endstop)", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 525, + "y": 1040, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nenable_endstop = load_bool('rotor_enable_endstop')\n\nif enable_endstop:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nautofocus = load_bool('cam_autofocus') ##change##\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\n##change##\nif focus_min == focus_max or autofocus:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef focus(f):\n ##change##\n if autofocus:\n camera('/camera/picam2_af')\n else:\n camera('/camera/picam2_focus?focus=' + str(f))\n ##change##\n\ndef photo(counter2):\n camera('/camera/picam2_take_photo')\n ##change##\n focus(focuslist[returning[0]])\n if returning[0] < len(focuslist) - 1:\n returning[0] += 1\n else:\n returning[0] = 0\n ##change##\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\n\ndef stack_photo(i):\n\n camera('/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n\ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n ##change##\n focus(focuslist[i+1])\n else:\n camera(focuslist[0])\n sleep(1.7)\n\ndef photo_stack():\n camera(focuslist[0])\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n\n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n\n focus_thread.start()\n photo_thread.start()\n\n focus_thread.join()\n photo_thread.join()\n\n\ndef move_motor(enable_endstop=False):\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle,enable_endstop)\n motorrun('rotor',rotor_angle,enable_endstop)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor(enable_endstop)\n sleep(load_float(\"cam_delay_before\"))\n\n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2140, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2380, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2180, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2220, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2100, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2180, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2220, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2140, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2100, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2420, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2460, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2340, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2340, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2420, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2380, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2260, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2260, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2500, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2500, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2540, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2300, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2540, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2300, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2680, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2720, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2840, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2880, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2920, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2680, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2720, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2840, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2920, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3120, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3120, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3160, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3160, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3240, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3240, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3280, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3280, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3320, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3320, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3360, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3360, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3400, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3400, + "wires": [] + }, + { + "id": "74e455136b5ca5dd", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 21, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3440, + "wires": [ + [ + "a4a89668ce4c9f05" + ] + ] + }, + { + "id": "3a74f653800eb831", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, + "width": 4, + "height": 1, + "name": "endstop2", + "label": "Endstop Turntable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3440, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2960, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2960, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2540, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2540, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2680, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2720, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2920, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2920, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2960, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2960, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3400, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3400, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3360, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3360, + "wires": [ + [] + ] + }, + { + "id": "21dc963d967d9c99", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3440, + "wires": [ + [ + "74e455136b5ca5dd" + ] + ] + }, + { + "id": "a4a89668ce4c9f05", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3440, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd", + "fe62e12d458db2d4" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2640, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2640, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761", + "fe62e12d458db2d4", + "aea4e51b20951560" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, + { + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "69885a9ce218eb71", + "type": "comment", + "z": "e43a27722b508115", + "name": "Coloritos", + "info": "", + "x": 100, + "y": 3740, + "wires": [] + }, + { + "id": "dc1cde67c3022e6b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3800, + "wires": [ + [ + "0dccca85770c7936" + ] + ] + }, + { + "id": "b63e8246ad14ad9d", + "type": "function", + "z": "e43a27722b508115", + "name": "interface-color", + "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 540, + "y": 3800, + "wires": [ + [] + ] + }, + { + "id": "b7044aa75196b521", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3800, + "wires": [ + [ + "dc1cde67c3022e6b" + ] + ] + }, + { + "id": "0dccca85770c7936", + "type": "ui_dropdown", + "z": "e43a27722b508115", + "name": "interface_color", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "Aburrido", + "value": "#097479", + "type": "str" + }, + { + "label": "Morado", + "value": "#790974", + "type": "str" + }, + { + "label": "Berenjena", + "value": "#79093c", + "type": "str" + }, + { + "label": "Azul", + "value": "#093c79 ", + "type": "str" + }, + { + "label": "Oliva", + "value": "#747909", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 360, + "y": 3800, + "wires": [ + [ + "b63e8246ad14ad9d" + ] + ] + }, + { + "id": "667950f6671bd1a0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 840, + "wires": [ + [ + "b82a1cbefad51cd8" + ] + ] + }, + { + "id": "5f32d7e78e368454", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "b82a1cbefad51cd8", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "hostname", + "label": "Hostname", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "mode": "text", + "delay": 300, + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 360, + "y": 840, + "wires": [ + [ + "5f32d7e78e368454" + ] + ] + }, + { + "id": "5fd155711e29b1b8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Monitoring", + "info": "", + "x": 100, + "y": 3860, + "wires": [] + }, + { + "id": "815702499384f118", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3920, + "wires": [ + [ + "bfdbdae28bf42ed4" + ] + ] + }, + { + "id": "464c8495f86daaa7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "datadog_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3920, + "wires": [ + [] + ] + }, + { + "id": "bfdbdae28bf42ed4", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "datadog_enable", + "label": "Enable Datadog", + "tooltip": "Enable Datadog monitoring", + "group": "33aff36289823faa", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3920, + "wires": [ + [ + "464c8495f86daaa7" + ] + ] + }, + { + "id": "f93ce2d26953341f", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "label": "Datadog Api Token", + "tooltip": "Datadog Api Token", + "group": "33aff36289823faa", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3960, + "wires": [ + [ + "647641e79884eb87" + ] + ] + }, + { + "id": "ee668e39d213070b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3960, + "wires": [ + [ + "f93ce2d26953341f" + ] + ] + }, + { + "id": "647641e79884eb87", + "type": "function", + "z": "e43a27722b508115", + "name": "datadog_api_token", + "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3960, + "wires": [ + [] + ] + }, + { + "id": "ff2dea1ab9cb7776", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 4", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3960, + "wires": [ + [ + "815702499384f118", + "ee668e39d213070b" + ] + ] + }, + { + "id": "a1b81e7fe94ad4e5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", + "outputs": 1, + "x": 530, + "y": 3740, + "wires": [ + [] + ] + }, + { + "id": "2f3a3c0e682ae862", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "restart_interface", + "group": "15edc2ce885dddb3", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "Restart Interface", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 340, + "y": 3740, + "wires": [ + [ + "a1b81e7fe94ad4e5" + ] + ] + }, + { + "id": "fe62e12d458db2d4", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = false;\n}\nelse{\n data = true;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "96e2b45a7102156b" + ] + ] + }, + { + "id": "96e2b45a7102156b", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_endstop_pushed", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "false", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "true", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 2060, + "wires": [ + [ + "df66caa5e0497e65" + ] + ] + }, + { + "id": "df66caa5e0497e65", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "aea4e51b20951560", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_endstop_pushed", + "label": "Reverse Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 2060, + "wires": [] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 10, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "cde61b7de9eeaba7", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "3ce32450.e0cffc", + "order": 9, + "width": 0, + "height": 0, + "passthru": false, + "label": "Expand Root", + "tooltip": "Sets the maximum space your SD card admits", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "expand", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 510, + "y": 1020, + "wires": [ + [ + "eab36487d201f867" + ] + ] + }, + { + "id": "eab36487d201f867", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "", + "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", + "outputs": 1, + "x": 690, + "y": 1020, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/update/2024-11S/meanwhile/routine.py b/update/2024-11S/meanwhile/routine.py new file mode 100644 index 0000000..0b822be --- /dev/null +++ b/update/2024-11S/meanwhile/routine.py @@ -0,0 +1,240 @@ +from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \ + load_bool, camera +from time import sleep, strftime, time +from subprocess import getoutput, run + +from zipfile import ZipFile, ZIP_DEFLATED +from os import system, uname +from os.path import isfile, getsize +import math +import threading +import numpy as np + +if load_str("status_internal_cam") == "no camera found" or load_str("status_internal_cam")[:5] == "Featu": + return + +#motorrun('rotor', 140, ES_enable=True, ES_start_state=True) +#motorrun('rotor', 10) + + + +save('status_internal_cam', 'Routine-preparing') +camera('/picam2_switch_mode?mode=1') + +save('cam_sharparea', False) +save('cam_features', False) + + +projectname = load_str("routine_projectname") +angle_max = load_int('rotor_anglemax') +angle_min = load_int('rotor_anglemin') +if load_bool('rotor_enable_endstop'): + angle_start = load_int('rotor_endstop_angle') + motorrun('rotor',angle_start/abs(angle_start) * 130, True, False) + +else: + angle_start = load_int('rotor_anglestart') + + +photocount = load_int('routine_photocount') + +autofocus = load_bool('cam_autofocus') ##change## +focus_min = load_float('cam_focus_min') +focus_max = load_float('cam_focus_max') +stacksize = load_int('cam_stacksize') +group_stack_photos = load_bool('group_stack_photos') + +telegram_enable = load_bool('telegram_enable') +if telegram_enable: + telegram_api_token = load_str('telegram_api_token') + telegram_client_id = load_str('telegram_client_id') + +##change## +if focus_min == focus_max || autofocus: + stacksize = 1 + +focuslist = [] +if stacksize == 1: + steps = 3 + int(abs(focus_max-focus_min)*0.8) +else: + steps = stacksize + +for i in range (steps): + focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1)) + +msg['focuslist'] = focuslist +msg['payload2'] = [] +counter = 0 + +basepath = '/home/pi/OpenScan/' +temppath = basepath + 'tmp2/preview.jpg' +zippath = basepath + 'tmp.zip' + +projectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname + +if isfile(zippath): + system('rm ' + zippath) +sleep(1) + +coordinates = create_coordinates(angle_min, angle_max, photocount) +coordinates = sort_spherical_coordinates_deg(coordinates) + +msg['payload'] = coordinates + +position_last = (angle_start, 0) + +zip = ZipFile(zippath, "a", ZIP_DEFLATED, allowZip64=True) + +hostname = str(uname()[1]) + +starttime = time() + +def get_eta(starttime, photocounter, count): + return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str( + int(photocount / counter * (time() - starttime))) + 's' + +def focus(f): + ##change## + if autofocus: + camera('/picam2_af') + else: + camera('/picam2_focus?focus=' + str(f)) + ##change## + +def photo(counter2): + camera('/picam2_take_photo') + ##change## + focus(focuslist[returning[0]]) + if returning[0] < len(focuslist) - 1: + returning[0] += 1 + else: + returning[0] = 0 + ##change## + zip.write(temppath, projectname + '_' + str(counter) + ".jpg") + + +def stack_photo(i): + + camera('/picam2_take_photo') + if group_stack_photos: + name = projectname + '_' + str(counter) + "/" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg' + else: + name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg' + zip.write(temppath, name) + +def stack_focus(i): + sleep(load_float('cam_shutter')/1000000*2) + if i < len(focuslist)-1: + ##change## + focus(focuslist[i+1]) + else: + camera(focuslist[0]) + sleep(1.7) + +def photo_stack(): + camera(focuslist[0]) + for i in range(len(focuslist)): + if load_str('status_internal_cam') == "Routine-stopping": + break + save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + "-F"+ str(i+1)) + + focus_thread = threading.Thread(target=stack_focus, args=(i,)) + photo_thread = threading.Thread(target=stack_photo, args=(i,)) + + focus_thread.start() + photo_thread.start() + + focus_thread.join() + photo_thread.join() + + +def move_motor(): + rotor_angle = position[0] - position_last[0] + msg['payload2'].append(rotor_angle) + #if abs(rotor_angle) > 180: + # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle + tt_angle = position_last[1] - position[1] + if tt_angle > 180: + tt_angle -= 360 + elif tt_angle < -180: + tt_angle += 360 + + motorrun('tt',tt_angle) + motorrun('rotor',rotor_angle) + return + + # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?! + + #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle)) + #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle)) + #tt_thread.start() + #rotor_thread.start() + #tt_thread.join() + #rotor_thread.join() + + +counter2 = 0 + +def check_diskspace(): + diskspace_threshold = load_int('diskspace_threshold') + diskspace = getoutput('df -h / | awk "{print $5}"').split('\n')[1] + available = int(float(diskspace.replace(' ','').split('G')[2])*1000) + if available < diskspace_threshold: + save('status_internal_cam', 'Routine-stopping') + return + +def send_telegram_message(message, telegram_api_token, telegram_client_id): + telegram_bot_path = '/usr/local/bin/send-telegram' + run([telegram_bot_path,"-a",telegram_api_token,"-c",telegram_client_id,"-m",message]) + +if telegram_enable: + telegram_message = "[START] " + hostname + " starting " + projectname + "(" + str(photocount) + " photos) ETA: " + try: + send_telegram_message(telegram_message, telegram_api_token, telegram_client_id) + except Exception as e: + print(e) +for position in coordinates: + counter += 1 + filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + ".jpg" + if load_str('status_internal_cam') == "Routine-stopping": + break + if counter < 6: + ETA = '' + + save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA) + if counter > 6: + check_diskspace() + + move_motor() + sleep(load_float("cam_delay_before")) + + if stacksize ==1: + returning = [counter2] + photo(returning) + counter2 = returning[0] + + else: + photo_stack() + + sleep(load_float("cam_delay_after")) + ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str( + int(photocount / counter * (time() - starttime))) + 's' + position_last = position + +zip.close() +try: + send_telegram_message("[STOP] " + hostname + " stop " + projectname, telegram_api_token, telegram_client_id) +except Exception as e: + print(e) +camera('/picam2_switch_mode?mode=0') + +save('status_internal_cam', 'Routine-done') + +motorrun('rotor', -position_last[0] ) +motorrun('tt', position_last[1]) + +save('status_internal_cam', '--READY--') + +system('mv ' + zippath + " " + basepath + "scans/" + projectcode + ".zip") + +return msg diff --git a/update/2024-11S/meanwhile/settings.js b/update/2024-11S/meanwhile/settings.js new file mode 100644 index 0000000..357b02b --- /dev/null +++ b/update/2024-11S/meanwhile/settings.js @@ -0,0 +1,512 @@ +/** + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT + * + * It can contain any valid JavaScript code that will get run when Node-RED + * is started. + * + * Lines that start with // are commented out. + * Each entry should be separated from the entries above and below by a comma ',' + * + * For more information about individual settings, refer to the documentation: + * https://nodered.org/docs/user-guide/runtime/configuration + * + * The settings are split into the following sections: + * - Flow File and User Directory Settings + * - Security + * - Server Settings + * - Runtime Settings + * - Editor Settings + * - Node Settings + * + **/ +process.env.HOSTNAME = require('os').hostname(); + +module.exports = { + +/******************************************************************************* + * Flow File and User Directory Settings + * - flowFile + * - credentialSecret + * - flowFilePretty + * - userDir + * - nodesDir + ******************************************************************************/ + + /** The file containing the flows. If not set, defaults to flows_.json **/ + flowFile: "flows.json", + + /** By default, credentials are encrypted in storage using a generated key. To + * specify your own secret, set the following property. + * If you want to disable encryption of credentials, set this property to false. + * Note: once you set this property, do not change it - doing so will prevent + * node-red from being able to decrypt your existing credentials and they will be + * lost. + */ + credentialSecret: false, + + /** By default, the flow JSON will be formatted over multiple lines making + * it easier to compare changes when using version control. + * To disable pretty-printing of the JSON set the following property to false. + */ + flowFilePretty: true, + + /** By default, all user data is stored in a directory called `.node-red` under + * the user's home directory. To use a different location, the following + * property can be used + */ + //userDir: '/home/nol/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + + /** Node-RED scans the `nodes` directory in the userDir to find local node files. + * The following property can be used to specify an additional directory to scan. + */ + //nodesDir: '/home/nol/.node-red/nodes', + +/******************************************************************************* + * Security + * - adminAuth + * - https + * - httpsRefreshInterval + * - requireHttps + * - httpNodeAuth + * - httpStaticAuth + ******************************************************************************/ + + /** To password protect the Node-RED editor and admin API, the following + * property can be used. See http://nodered.org/docs/security.html for details. + */ + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + /** The following property can be used to enable HTTPS + * This property can be either an object, containing both a (private) key + * and a (public) certificate, or a function that returns such an object. + * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + * for details of its contents. + */ + + /** Option 1: static object */ + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + + /** Option 2: function that returns the HTTP configuration object */ + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + /** If the `https` setting is a function, the following setting can be used + * to set how often, in hours, the function will be called. That can be used + * to refresh any certificates. + */ + //httpsRefreshInterval : 12, + + /** The following property can be used to cause insecure HTTP connections to + * be redirected to HTTPS. + */ + //requireHttps: true, + + /** To password protect the node-defined HTTP endpoints (httpNodeRoot), + * including node-red-dashboard, or the static content (httpStatic), the + * following properties can be used. + * The `pass` field is a bcrypt hash of the password. + * See http://nodered.org/docs/security.html#generating-the-password-hash + */ + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + +/******************************************************************************* + * Server Settings + * - uiPort + * - uiHost + * - apiMaxLength + * - httpServerOptions + * - httpAdminRoot + * - httpAdminMiddleware + * - httpNodeRoot + * - httpNodeCors + * - httpNodeMiddleware + * - httpStatic + * - httpStaticRoot + ******************************************************************************/ + + /** the tcp port that the Node-RED web server is listening on */ + uiPort: process.env.PORT || 80, + + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. + * To listen on all IPv6 addresses, set uiHost to "::", + * The following property can be used to listen on a specific interface. For + * example, the following would only allow connections from the local machine. + */ + //uiHost: "127.0.0.1", + + /** The maximum size of HTTP request that will be accepted by the runtime api. + * Default: 5mb + */ + //apiMaxLength: '5mb', + + /** The following property can be used to pass custom options to the Express.js + * server used by Node-RED. For a full list of available options, refer + * to http://expressjs.com/en/api.html#app.settings.table + */ + //httpServerOptions: { }, + + /** By default, the Node-RED UI is available at http://localhost:1880/ + * The following property can be used to specify a different root path. + * If set to false, this is disabled. + */ + httpAdminRoot: '/editor', + + /** The following property can be used to add a custom middleware function + * in front of all admin http routes. For example, to set custom http + * headers. It can be a single function or an array of middleware functions. + */ + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + + /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. + * By default, these are served relative to '/'. The following property + * can be used to specifiy a different root path. If set to false, this is + * disabled. + */ + //httpNodeRoot: '/red-nodes', + + /** The following property can be used to configure cross-origin resource sharing + * in the HTTP nodes. + * See https://github.com/troygoode/node-cors#configuration-options for + * details on its contents. The following is a basic permissive set of options: + */ + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + /** If you need to set an http proxy please set an environment variable + * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + * For example - http_proxy=http://myproxy.com:8080 + * (Setting it here will have no effect) + * You may also specify no_proxy (or NO_PROXY) to supply a comma separated + * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + */ + + /** The following property can be used to add a custom middleware function + * in front of all http in nodes. This allows custom authentication to be + * applied to all http in nodes, or any other sort of common request processing. + * It can be a single function or an array of middleware functions. + */ + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + /** When httpAdminRoot is used to move the UI to a different root path, the + * following property can be used to identify a directory of static content + * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot + */ + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + +/******************************************************************************* + * Runtime Settings + * - lang + * - logging + * - contextStorage + * - exportGlobalContextKeys + * - externalModules + ******************************************************************************/ + + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", + + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, + + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, + + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: [], + // denyList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, + + +/******************************************************************************* + * Editor Settings + * - disableEditor + * - editorTheme + ******************************************************************************/ + + /** The following property can be used to disable the editor. The admin API + * is not affected by this option. To disable both the editor and the admin + * API, use either the httpRoot or httpAdminRoot properties + */ + //disableEditor: false, + + /** Customising the editor + * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes + * for all available options. + */ + editorTheme: { + /** The following property can be used to set a custom theme for the editor. + * See https://github.com/node-red-contrib-themes/theme-collection for + * a collection of themes to chose from. + */ + //theme: "", + palette: { + /** The following property can be used to order the categories in the editor + * palette. If a node's category is not in the list, the category will get + * added to the end of the palette. + * If not set, the following default order is used: + */ + //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + }, + projects: { + /** To enable the Projects feature, set this value to true */ + enabled: false, + workflow: { + /** Set the default projects workflow mode. + * - manual - you must manually commit changes + * - auto - changes are automatically committed + * This can be overridden per-user from the 'Git config' + * section of 'User Settings' within the editor + */ + mode: "manual" + } + }, + codeEditor: { + /** Select the text editor component used by the editor. + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired + */ + lib: "monaco", + options: { + /** The follow options only apply if the editor is set to "monaco" + * + * theme - must match the file name of a theme in + * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme + * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" + */ + theme: "vs", + /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html + */ + //fontSize: 14, + //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", + //fontLigatures: true, + } + } + }, + +/******************************************************************************* + * Node Settings + * - fileWorkingDirectory + * - functionGlobalContext + * - functionExternalModules + * - nodeMessageBufferMaxLength + * - ui (for use with Node-RED Dashboard) + * - debugUseColors + * - debugMaxLength + * - execMaxBufferSize + * - httpRequestTimeout + * - mqttReconnectTime + * - serialReconnectTime + * - socketReconnectTime + * - socketTimeout + * - tcpMsgQueueSize + * - inboundWebSocketTimeout + * - tlsConfigDisableLocalFiles + * - webSocketNodeVerifyClient + ******************************************************************************/ + + /** The working directory to handle relative file paths from within the File nodes + * defaults to the working directory of the Node-RED process. + */ + //fileWorkingDirectory: "", + + /** Allow the Function node to load additional npm modules directly */ + functionExternalModules: true, + + /** The following property can be used to set predefined values in Global Context. + * This allows extra node modules to be made available with in Function node. + * For example, the following: + * functionGlobalContext: { os:require('os') } + * will allow the `os` module to be accessed in a Function node using: + * global.get("os") + */ +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, + /** The maximum number of messages nodes will buffer internally as part of their + * operation. This applies across a range of nodes that operate on message sequences. + * defaults to no limit. A value of 0 also means no limit is applied. + */ + //nodeMessageBufferMaxLength: 0, + + /** If you installed the optional node-red-dashboard you can set it's path + * relative to httpNodeRoot + * Other optional properties include + * readOnly:{boolean}, + * middleware:{function or array}, (req,res,next) - http middleware + * ioMiddleware:{function or array}, (socket,next) - socket.io middleware + */ + ui: { path: "" }, + + /** Colourise the console output of the debug node */ + //debugUseColors: true, + + /** The maximum length, in characters, of any message sent to the debug sidebar tab */ + debugMaxLength: 1000, + + /** Maximum buffer size for the exec node. Defaults to 10Mb */ + //execMaxBufferSize: 10000000, + + /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ + //httpRequestTimeout: 120000, + + /** Retry time in milliseconds for MQTT connections */ + mqttReconnectTime: 15000, + + /** Retry time in milliseconds for Serial port connections */ + serialReconnectTime: 15000, + + /** Retry time in milliseconds for TCP socket connections */ + //socketReconnectTime: 10000, + + /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ + //socketTimeout: 120000, + + /** Maximum number of messages to wait in queue while attempting to connect to TCP socket + * defaults to 1000 + */ + //tcpMsgQueueSize: 2000, + + /** Timeout in milliseconds for inbound WebSocket connections that do not + * match any configured node. Defaults to 5000 + */ + //inboundWebSocketTimeout: 5000, + + /** To disable the option for using local files for storing keys and + * certificates in the TLS configuration node, set this to true. + */ + //tlsConfigDisableLocalFiles: true, + + /** The following property can be used to verify websocket connection attempts. + * This allows, for example, the HTTP request headers to be checked to ensure + * they include valid authentication information. + */ + //webSocketNodeVerifyClient: function(info) { + // /** 'info' has three properties: + // * - origin : the value in the Origin header + // * - req : the HTTP request + // * - secure : true if req.connection.authorized or req.connection.encrypted is set + // * + // * The function should return true if the connection should be accepted, false otherwise. + // * + // * Alternatively, if this function is defined to accept a second argument, callback, + // * it can be used to verify the client asynchronously. + // * The callback takes three arguments: + // * - result : boolean, whether to accept the connection or not + // * - code : if result is false, the HTTP error status to return + // * - reason: if result is false, the HTTP reason string to return + // */ + //}, +} diff --git a/update/2024-11S/meanwhile/start.sh b/update/2024-11S/meanwhile/start.sh new file mode 100755 index 0000000..223ffae --- /dev/null +++ b/update/2024-11S/meanwhile/start.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +flow_dir="/home/pi/OpenScan/settings/.node-red" +flows_json_file=$flow_dir/flows.json +hostname=`hostname` +echo $hostname +session_token=`echo $RANDOM | md5sum | head -c 20` +echo $session_token > /home/pi/OpenScan/settings/session_token + +cat $flow_dir/flows.json.tmpl|sed "s|{{ hostname }}|$hostname.local|g" > $flows_json_file +sed -i "s|{{ session_token }}|$session_token|g" $flows_json_file diff --git a/update/2024-11S/stable/OpenScan.py b/update/2024-11S/stable/OpenScan.py new file mode 100644 index 0000000..681c78d --- /dev/null +++ b/update/2024-11S/stable/OpenScan.py @@ -0,0 +1,316 @@ +basepath = '/home/pi/OpenScan/' +from os.path import isfile +import os + +def load_bool(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + if value == '1' or value == 'True' or value =='true': + value = True + else: + value = False + return value + +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + + return True + + +def load_str(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + return value + +def load_int(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = int(file.read().replace('\n','')) + return value + +def load_float(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = float(file.read().replace('\n','')) + return value + +def save(name, value): + filename = basepath+'settings/'+name + with open(filename, 'w+') as file: + file.write(str(value)) + return + +def OpenScanCloud(cmd, msg): + from requests import get + osc_user = 'openscan' + osc_pw = 'free' + osc_server = 'http://openscanfeedback.dnsuser.de:1334/' + + try: + r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) + except: + r = type('obj', (object,), {'status_code' : 404, 'text':None}) + return r + +def camera(cmd, msg = {}): + from requests import get + flask = 'http://127.0.0.1:1312/' + try: + r = get(flask + cmd, params=msg) + return r.status_code + except: + return 400 + +def motorrun(motor,angle,ES_enable=False,ES_start_state = True): + #motor can be "rotor", "tt" or "extra" + import RPi.GPIO as GPIO + from time import sleep + from math import cos + msg = {'cmd':'set'} + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + + spr = load_int(motor + '_stepsperrotation') + dirpin = load_int('pin_' + motor + '_dir') + steppin = load_int('pin_' + motor +'_step') + ES_pin = load_int('pin_' + motor + '_endstop') + dir = load_int(motor + '_dir') + ramp = load_int(motor + '_accramp') + acc = load_float(motor + '_acc') + delay_init = load_float(motor + '_delay') + delay = delay_init + + step_count=int(angle*spr/360) * dir + GPIO.setup(dirpin, GPIO.OUT) + GPIO.setup(steppin, GPIO.OUT) + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + + if (step_count>0): + GPIO.output(dirpin, GPIO.HIGH) + if(step_count<0): + GPIO.output(dirpin, GPIO.LOW) + step_count=-step_count + for x in range(step_count): + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + + GPIO.output(steppin, GPIO.HIGH) + if x<=ramp and x<=step_count/2: + delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) + #delay=delay_init+(ramp-x)*(delay_init)/acc + elif step_count-x<=ramp and x>step_count/2: + delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) + #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc + else: + delay = delay_init + sleep(delay) + GPIO.output(steppin, GPIO.LOW) + sleep(delay) + +def ringlight(number,state): + import RPi.GPIO as GPIO + msg = {'cmd':'set'} + pin = load_int('pin_ringlight' + str(number)) + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(pin, GPIO.OUT) + GPIO.output(pin, state) + +def take_photo(file): + from os import system + filepath = basepath + file + + model=load_str('model') + + + + shutter = str(load_int('cam_shutter')) + saturation = load_str('cam_saturation') + contrast = load_str('cam_contrast') + awbg_red = load_str('cam_awbg_red') + awbg_blue = load_str('cam_awbg_blue') + gain = load_str('cam_gain') + quality = load_int('cam_jpeg_quality') + filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' + #width = load_str('cam_resx') + #height = load_str('cam_resy') + timeout = load_str('cam_timeout') + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + AF = load_bool('cam_AFmode') + camera = load_str('camera') + + + if camera == 'imx519' and AF == True: + autofocus = ' --autofocus ' + else: + autofocus = '' + + if camera == "usb_webcam": + cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 + else: + cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' + # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + + system(cmd) + return cmd + +def get_points(samples=1): + from math import pi, sqrt, acos, atan2, cos, sin + + points = [] + phi = pi * (3. - sqrt(5.)) + for i in range(int(samples)): + y = 1 - (i / float(samples - 1)) * 2 + radius = sqrt(1 - y * y) + theta = phi * i + x = cos(theta) * radius + z = sin(theta) * radius + r=sqrt(x*x+y*y+z*z) + theta_neu=acos(z/r)*180/pi + phi_neu=atan2(y,x)*180/pi + points.append((theta_neu-90,phi_neu)) + points.sort() + return points + +def create_coordinates(angle_min, angle_max,point_count): + point_count_final=point_count + if angle_max < angle_min: + a = angle_min + angle_min = angle_max + angle_max = a + point_count=point_count*90/(angle_max-angle_min) + actual_points=0 + while actual_pointsangle_min and x20: + point_count=point_count+3 + else: + point_count=point_count+1 + return filtered + + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/2024-11S/stable/config.txt b/update/2024-11S/stable/config.txt new file mode 100755 index 0000000..cc525ae --- /dev/null +++ b/update/2024-11S/stable/config.txt @@ -0,0 +1,85 @@ +# For more options and information see +# http://rpf.io/configtxt +# Some settings may impact device functionality. See link above for details + +# uncomment if you get no picture on HDMI for a default "safe" mode +#hdmi_safe=1 + +# uncomment the following to adjust overscan. Use positive numbers if console +# goes off screen, and negative if there is too much border +#overscan_left=16 +#overscan_right=16 +#overscan_top=16 +#overscan_bottom=16 + +# uncomment to force a console size. By default it will be display's size minus +# overscan. +#framebuffer_width=1280 +#framebuffer_height=720 + +# uncomment if hdmi display is not detected and composite is being output +#hdmi_force_hotplug=1 + +# uncomment to force a specific HDMI mode (this will force VGA) +#hdmi_group=1 +#hdmi_mode=1 + +# uncomment to force a HDMI mode rather than DVI. This can make audio work in +# DMT (computer monitor) modes +#hdmi_drive=2 + +# uncomment to increase signal to HDMI, if you have interference, blanking, or +# no display +#config_hdmi_boost=4 + +# uncomment for composite PAL +#sdtv_mode=2 + +#uncomment to overclock the arm. 700 MHz is the default. +#arm_freq=800 + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Uncomment this to enable infrared communication. +#dtoverlay=gpio-ir,gpio_pin=17 +#dtoverlay=gpio-ir-tx,gpio_pin=18 + +# Additional overlays and parameters are documented /boot/overlays/README + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Disable compensation for displays with overscan +disable_overscan=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] + +[pi4] +# Run as fast as firmware / board allows +arm_boost=1 + +[all] +camera_auto_detect=0 +gpu_mem=256 +dtoverlay=vc4-fkms-v3d +dtoverlay=imx519 +#dtoverlay=imx519,media-controller=1 diff --git a/update/2024-11S/stable/fla.py b/update/2024-11S/stable/fla.py new file mode 100644 index 0000000..683ca10 --- /dev/null +++ b/update/2024-11S/stable/fla.py @@ -0,0 +1,394 @@ +from flask import Flask, make_response, jsonify, request, abort, redirect +from flask_restx import Resource, Api, Namespace +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont +from time import sleep, time +import shutil +from OpenScan import load_int, load_float, load_bool, ringlight +import RPi.GPIO as GPIO +from math import sqrt +import os +import math +from skimage import io, feature, color, transform +import numpy as np +from scipy import ndimage +import socket + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +app = Flask(__name__) +api = Api(app) + +# Create a namespace for system operations +system_ns = Namespace('system', description='System operations') +camera_ns = Namespace('camera', description='Camera operations') +api.add_namespace(system_ns) +api.add_namespace(camera_ns) + +basedir = '/home/pi/OpenScan/' +timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + + +################################################################################################################### + + + +@system_ns.route('/shutdown') +class Shutdown(Resource): + @system_ns.doc(params={'token': 'Shutdown token for authentication'}) + def get(self): + '''Shutdown the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('shutdown -h now') + return {'message': 'Shutting down'}, 200 + else: + return redirect("http://" + hostname, code=302) + +################################################################################################################### + +@system_ns.route('/reboot') +class Reboot(Resource): + @system_ns.doc(params={'token': 'Reboot token for authentication'}) + def get(self): + '''Reboot the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('reboot -h') + return {'message': 'Rebooting'}, 200 + else: + return redirect("http://" + hostname, code=302) +################################################################################################################### + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) + + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') + + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wait 3-5s + return {'message': 'Auto focus triggered'}, 200 + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + +if __name__ == '__main__': +# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) + app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/2024-11S/stable/flows.json.tmpl b/update/2024-11S/stable/flows.json.tmpl new file mode 100644 index 0000000..9c9d94a --- /dev/null +++ b/update/2024-11S/stable/flows.json.tmpl @@ -0,0 +1,9378 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "#097479", + "value": "#097479", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#097479", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "#097479", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 460, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 380, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 380, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 720, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 720, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/camera/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 525, + "y": 1040, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2100, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2340, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2140, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2180, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2060, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2140, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2180, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2100, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2060, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2380, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2420, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2300, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2420, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2300, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2380, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2340, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2220, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2220, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2460, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2500, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2260, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2500, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2260, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2600, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2640, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2760, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2800, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2600, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2640, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2760, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2800, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2840, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3000, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3000, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3040, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3040, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3120, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3120, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3160, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3160, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3240, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3240, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3280, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3280, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3320, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3320, + "wires": [] + }, + { + "id": "74e455136b5ca5dd", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 21, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3360, + "wires": [ + [ + "a4a89668ce4c9f05" + ] + ] + }, + { + "id": "3a74f653800eb831", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, + "width": 4, + "height": 1, + "name": "endstop2", + "label": "Endstop Turntable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3360, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2880, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2600, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2600, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2760, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2800, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3000, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3000, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3040, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3040, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "21dc963d967d9c99", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3360, + "wires": [ + [ + "74e455136b5ca5dd" + ] + ] + }, + { + "id": "a4a89668ce4c9f05", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3360, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2560, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2560, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, +{ + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, +{ + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 9, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + } +] diff --git a/update/2024-11S/stable/settings.js b/update/2024-11S/stable/settings.js new file mode 100644 index 0000000..357b02b --- /dev/null +++ b/update/2024-11S/stable/settings.js @@ -0,0 +1,512 @@ +/** + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT + * + * It can contain any valid JavaScript code that will get run when Node-RED + * is started. + * + * Lines that start with // are commented out. + * Each entry should be separated from the entries above and below by a comma ',' + * + * For more information about individual settings, refer to the documentation: + * https://nodered.org/docs/user-guide/runtime/configuration + * + * The settings are split into the following sections: + * - Flow File and User Directory Settings + * - Security + * - Server Settings + * - Runtime Settings + * - Editor Settings + * - Node Settings + * + **/ +process.env.HOSTNAME = require('os').hostname(); + +module.exports = { + +/******************************************************************************* + * Flow File and User Directory Settings + * - flowFile + * - credentialSecret + * - flowFilePretty + * - userDir + * - nodesDir + ******************************************************************************/ + + /** The file containing the flows. If not set, defaults to flows_.json **/ + flowFile: "flows.json", + + /** By default, credentials are encrypted in storage using a generated key. To + * specify your own secret, set the following property. + * If you want to disable encryption of credentials, set this property to false. + * Note: once you set this property, do not change it - doing so will prevent + * node-red from being able to decrypt your existing credentials and they will be + * lost. + */ + credentialSecret: false, + + /** By default, the flow JSON will be formatted over multiple lines making + * it easier to compare changes when using version control. + * To disable pretty-printing of the JSON set the following property to false. + */ + flowFilePretty: true, + + /** By default, all user data is stored in a directory called `.node-red` under + * the user's home directory. To use a different location, the following + * property can be used + */ + //userDir: '/home/nol/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + + /** Node-RED scans the `nodes` directory in the userDir to find local node files. + * The following property can be used to specify an additional directory to scan. + */ + //nodesDir: '/home/nol/.node-red/nodes', + +/******************************************************************************* + * Security + * - adminAuth + * - https + * - httpsRefreshInterval + * - requireHttps + * - httpNodeAuth + * - httpStaticAuth + ******************************************************************************/ + + /** To password protect the Node-RED editor and admin API, the following + * property can be used. See http://nodered.org/docs/security.html for details. + */ + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + /** The following property can be used to enable HTTPS + * This property can be either an object, containing both a (private) key + * and a (public) certificate, or a function that returns such an object. + * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + * for details of its contents. + */ + + /** Option 1: static object */ + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + + /** Option 2: function that returns the HTTP configuration object */ + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + /** If the `https` setting is a function, the following setting can be used + * to set how often, in hours, the function will be called. That can be used + * to refresh any certificates. + */ + //httpsRefreshInterval : 12, + + /** The following property can be used to cause insecure HTTP connections to + * be redirected to HTTPS. + */ + //requireHttps: true, + + /** To password protect the node-defined HTTP endpoints (httpNodeRoot), + * including node-red-dashboard, or the static content (httpStatic), the + * following properties can be used. + * The `pass` field is a bcrypt hash of the password. + * See http://nodered.org/docs/security.html#generating-the-password-hash + */ + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + +/******************************************************************************* + * Server Settings + * - uiPort + * - uiHost + * - apiMaxLength + * - httpServerOptions + * - httpAdminRoot + * - httpAdminMiddleware + * - httpNodeRoot + * - httpNodeCors + * - httpNodeMiddleware + * - httpStatic + * - httpStaticRoot + ******************************************************************************/ + + /** the tcp port that the Node-RED web server is listening on */ + uiPort: process.env.PORT || 80, + + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. + * To listen on all IPv6 addresses, set uiHost to "::", + * The following property can be used to listen on a specific interface. For + * example, the following would only allow connections from the local machine. + */ + //uiHost: "127.0.0.1", + + /** The maximum size of HTTP request that will be accepted by the runtime api. + * Default: 5mb + */ + //apiMaxLength: '5mb', + + /** The following property can be used to pass custom options to the Express.js + * server used by Node-RED. For a full list of available options, refer + * to http://expressjs.com/en/api.html#app.settings.table + */ + //httpServerOptions: { }, + + /** By default, the Node-RED UI is available at http://localhost:1880/ + * The following property can be used to specify a different root path. + * If set to false, this is disabled. + */ + httpAdminRoot: '/editor', + + /** The following property can be used to add a custom middleware function + * in front of all admin http routes. For example, to set custom http + * headers. It can be a single function or an array of middleware functions. + */ + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + + /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. + * By default, these are served relative to '/'. The following property + * can be used to specifiy a different root path. If set to false, this is + * disabled. + */ + //httpNodeRoot: '/red-nodes', + + /** The following property can be used to configure cross-origin resource sharing + * in the HTTP nodes. + * See https://github.com/troygoode/node-cors#configuration-options for + * details on its contents. The following is a basic permissive set of options: + */ + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + /** If you need to set an http proxy please set an environment variable + * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + * For example - http_proxy=http://myproxy.com:8080 + * (Setting it here will have no effect) + * You may also specify no_proxy (or NO_PROXY) to supply a comma separated + * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + */ + + /** The following property can be used to add a custom middleware function + * in front of all http in nodes. This allows custom authentication to be + * applied to all http in nodes, or any other sort of common request processing. + * It can be a single function or an array of middleware functions. + */ + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + /** When httpAdminRoot is used to move the UI to a different root path, the + * following property can be used to identify a directory of static content + * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot + */ + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + +/******************************************************************************* + * Runtime Settings + * - lang + * - logging + * - contextStorage + * - exportGlobalContextKeys + * - externalModules + ******************************************************************************/ + + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", + + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, + + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, + + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: [], + // denyList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, + + +/******************************************************************************* + * Editor Settings + * - disableEditor + * - editorTheme + ******************************************************************************/ + + /** The following property can be used to disable the editor. The admin API + * is not affected by this option. To disable both the editor and the admin + * API, use either the httpRoot or httpAdminRoot properties + */ + //disableEditor: false, + + /** Customising the editor + * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes + * for all available options. + */ + editorTheme: { + /** The following property can be used to set a custom theme for the editor. + * See https://github.com/node-red-contrib-themes/theme-collection for + * a collection of themes to chose from. + */ + //theme: "", + palette: { + /** The following property can be used to order the categories in the editor + * palette. If a node's category is not in the list, the category will get + * added to the end of the palette. + * If not set, the following default order is used: + */ + //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + }, + projects: { + /** To enable the Projects feature, set this value to true */ + enabled: false, + workflow: { + /** Set the default projects workflow mode. + * - manual - you must manually commit changes + * - auto - changes are automatically committed + * This can be overridden per-user from the 'Git config' + * section of 'User Settings' within the editor + */ + mode: "manual" + } + }, + codeEditor: { + /** Select the text editor component used by the editor. + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired + */ + lib: "monaco", + options: { + /** The follow options only apply if the editor is set to "monaco" + * + * theme - must match the file name of a theme in + * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme + * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" + */ + theme: "vs", + /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html + */ + //fontSize: 14, + //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", + //fontLigatures: true, + } + } + }, + +/******************************************************************************* + * Node Settings + * - fileWorkingDirectory + * - functionGlobalContext + * - functionExternalModules + * - nodeMessageBufferMaxLength + * - ui (for use with Node-RED Dashboard) + * - debugUseColors + * - debugMaxLength + * - execMaxBufferSize + * - httpRequestTimeout + * - mqttReconnectTime + * - serialReconnectTime + * - socketReconnectTime + * - socketTimeout + * - tcpMsgQueueSize + * - inboundWebSocketTimeout + * - tlsConfigDisableLocalFiles + * - webSocketNodeVerifyClient + ******************************************************************************/ + + /** The working directory to handle relative file paths from within the File nodes + * defaults to the working directory of the Node-RED process. + */ + //fileWorkingDirectory: "", + + /** Allow the Function node to load additional npm modules directly */ + functionExternalModules: true, + + /** The following property can be used to set predefined values in Global Context. + * This allows extra node modules to be made available with in Function node. + * For example, the following: + * functionGlobalContext: { os:require('os') } + * will allow the `os` module to be accessed in a Function node using: + * global.get("os") + */ +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, + /** The maximum number of messages nodes will buffer internally as part of their + * operation. This applies across a range of nodes that operate on message sequences. + * defaults to no limit. A value of 0 also means no limit is applied. + */ + //nodeMessageBufferMaxLength: 0, + + /** If you installed the optional node-red-dashboard you can set it's path + * relative to httpNodeRoot + * Other optional properties include + * readOnly:{boolean}, + * middleware:{function or array}, (req,res,next) - http middleware + * ioMiddleware:{function or array}, (socket,next) - socket.io middleware + */ + ui: { path: "" }, + + /** Colourise the console output of the debug node */ + //debugUseColors: true, + + /** The maximum length, in characters, of any message sent to the debug sidebar tab */ + debugMaxLength: 1000, + + /** Maximum buffer size for the exec node. Defaults to 10Mb */ + //execMaxBufferSize: 10000000, + + /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ + //httpRequestTimeout: 120000, + + /** Retry time in milliseconds for MQTT connections */ + mqttReconnectTime: 15000, + + /** Retry time in milliseconds for Serial port connections */ + serialReconnectTime: 15000, + + /** Retry time in milliseconds for TCP socket connections */ + //socketReconnectTime: 10000, + + /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ + //socketTimeout: 120000, + + /** Maximum number of messages to wait in queue while attempting to connect to TCP socket + * defaults to 1000 + */ + //tcpMsgQueueSize: 2000, + + /** Timeout in milliseconds for inbound WebSocket connections that do not + * match any configured node. Defaults to 5000 + */ + //inboundWebSocketTimeout: 5000, + + /** To disable the option for using local files for storing keys and + * certificates in the TLS configuration node, set this to true. + */ + //tlsConfigDisableLocalFiles: true, + + /** The following property can be used to verify websocket connection attempts. + * This allows, for example, the HTTP request headers to be checked to ensure + * they include valid authentication information. + */ + //webSocketNodeVerifyClient: function(info) { + // /** 'info' has three properties: + // * - origin : the value in the Origin header + // * - req : the HTTP request + // * - secure : true if req.connection.authorized or req.connection.encrypted is set + // * + // * The function should return true if the connection should be accepted, false otherwise. + // * + // * Alternatively, if this function is defined to accept a second argument, callback, + // * it can be used to verify the client asynchronously. + // * The callback takes three arguments: + // * - result : boolean, whether to accept the connection or not + // * - code : if result is false, the HTTP error status to return + // * - reason: if result is false, the HTTP reason string to return + // */ + //}, +} diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json new file mode 100644 index 0000000..c92ef5b --- /dev/null +++ b/update/2024-11S/update.json @@ -0,0 +1,84 @@ +{ + "stable": { + "1": { + "src": "stable/fla.py", + "dst": "/home/pi/OpenScan/files/fla.py", + "filesize": 13829 + }, + "2": { + "src": "stable/OpenScan.py", + "dst": "/usr/lib/python3/dist-packages/OpenScan.py", + "filesize": 10253 + }, + "3": { + "src": "stable/config.txt", + "dst": "/boot/config.txt", + "filesize": 2185 + }, + "4": { + "src": "stable/flows.json.tmpl", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", + "filesize": 325787 + }, + "5": { + "src": "stable/settings.js", + "dst": "/root/.node-red/settings.js", + "filesize": 21248 + } + }, + + "beta": { + "1": { + "src": "beta/fla.py", + "dst": "/home/pi/OpenScan/files/fla.py", + "filesize": 13012 + }, + "2": { + "src": "beta/OpenScan.py", + "dst": "/usr/lib/python3/dist-packages/OpenScan.py", + "filesize": 10253 + }, + "3": { + "src": "beta/config.txt", + "dst": "/boot/config.txt", + "filesize": 2185 + }, + "4": { + "src": "beta/flows.json.tmpl", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", + "filesize": 337413 + }, + "5": { + "src": "beta/settings.js", + "dst": "/root/.node-red/settings.js", + "filesize": 21248 + } + }, + "meanwhile": { + "1": { + "src": "meanwhile/fla.py", + "dst": "/home/pi/OpenScan/files/fla.py", + "filesize": 13012 + }, + "2": { + "src": "meanwhile/OpenScan.py", + "dst": "/usr/lib/python3/dist-packages/OpenScan.py", + "filesize": 10322 + }, + "3": { + "src": "meanwhile/config.txt", + "dst": "/boot/config.txt", + "filesize": 2185 + }, + "4": { + "src": "meanwhile/flows.json.tmpl", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", + "filesize": 340275 + }, + "5": { + "src": "meanwhile/settings.js", + "dst": "/root/.node-red/settings.js", + "filesize": 21248 + } + } +} \ No newline at end of file From 61ac413275a0c3155bcc1f43a6241fe28dca0696 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 15 Sep 2024 17:55:25 +0200 Subject: [PATCH 04/38] update changelog --- docs/changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index af4cd9b..c9ff705 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,11 @@ # Changelog +### 2024-11S +* fixed: shutdown and reboot button in the UI +* Added Swagger UI to help build, design and document the API +* Fixed the FKMSGate +* New update mechanism: Releases have their own version with their own stable, beta and meanwhile flavours. + ### 2024-02-26 * Fixed: Without telegram configured it was impossible to finish a scan (hanged on finish). But who in their sanity would not use telegram if available right? * After a reboot/shutdown, if you forgot to close that tab it will not trigger a disaster when you are scanning in the future From 1a1d7c6ddb5943c891bc47ac2fc193b9d20fed6a Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 21 Sep 2024 22:26:21 +0200 Subject: [PATCH 05/38] add motor_run endpoint --- update/2024-11S/stable/fla.py | 41 ++++++++++++++++++++++++++++++++++- update/2024-11S/update.json | 2 +- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/update/2024-11S/stable/fla.py b/update/2024-11S/stable/fla.py index 683ca10..be03235 100644 --- a/update/2024-11S/stable/fla.py +++ b/update/2024-11S/stable/fla.py @@ -4,7 +4,7 @@ from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time import shutil -from OpenScan import load_int, load_float, load_bool, ringlight +from OpenScan import load_int, load_float, load_bool, ringlight, motorrun import RPi.GPIO as GPIO from math import sqrt import os @@ -23,8 +23,10 @@ # Create a namespace for system operations system_ns = Namespace('system', description='System operations') camera_ns = Namespace('camera', description='Camera operations') +motor_ns = Namespace('motor', description='Motor operations') api.add_namespace(system_ns) api.add_namespace(camera_ns) +api.add_namespace(motor_ns) basedir = '/home/pi/OpenScan/' timer = time() @@ -384,11 +386,48 @@ def get(self): picam2.set_controls({"AfMode": 1, "AfTrigger": 0}) # --> wait 3-5s return {'message': 'Auto focus triggered'}, 200 +@motor_ns.route('/motor_run') +class MotorRun(Resource): + ''' + Run a motor + ''' + @motor_ns.doc(params={ + 'motor': 'Motor name (rotor, tt, extra)', + 'angle': 'Angle to rotate (integer)', + 'ES_enable': 'Enable endstop (optional, boolean)', + 'ES_start_state': 'Endstop start state (optional, boolean)' + }) + @motor_ns.response(400, 'Bad Request') + def get(self): + '''Run a motor''' + motor = request.args.get('motor') + if not motor: + return {'error': 'Motor parameter is required'}, 400 + if motor not in ['rotor', 'tt', 'extra']: + return {'error': 'Invalid motor name'}, 400 + + try: + angle = int(request.args.get('angle')) + except (TypeError, ValueError): + return {'error': 'Angle must be an integer'}, 400 + + ES_enable = request.args.get('ES_enable', 'false').lower() == 'true' + ES_start_state = request.args.get('ES_start_state', 'true').lower() == 'true' + + try: + motorrun(motor, angle, ES_enable, ES_start_state) + except Exception as e: + return {'error': f'Error running motor: {str(e)}'}, 500 + + return {'message': f'Motor {motor} run to {angle} degrees'}, 200 + + @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') + if __name__ == '__main__': # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index c92ef5b..e632f87 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -3,7 +3,7 @@ "1": { "src": "stable/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 13829 + "filesize": 15180 }, "2": { "src": "stable/OpenScan.py", From ac729cda0f9b4416d944947df2d028253fbcb0e9 Mon Sep 17 00:00:00 2001 From: Stealth Date: Mon, 23 Sep 2024 22:17:22 +0200 Subject: [PATCH 06/38] update api to v1 --- update/2024-11S/stable/OpenScan.py | 36 ++++++++++-- update/2024-11S/stable/fla.py | 81 ++++++++++++++++++++++---- update/2024-11S/stable/flows.json.tmpl | 16 ++--- update/2024-11S/update.json | 4 +- 4 files changed, 113 insertions(+), 24 deletions(-) diff --git a/update/2024-11S/stable/OpenScan.py b/update/2024-11S/stable/OpenScan.py index 681c78d..9394eef 100644 --- a/update/2024-11S/stable/OpenScan.py +++ b/update/2024-11S/stable/OpenScan.py @@ -78,10 +78,8 @@ def add_wifi_network(ssid, password, country): with open(conf_file, "w") as f: f.write(updated_content) os.system("sudo systemctl restart wpa_supplicant@wlan0") - return True - def load_str(name): filename = basepath+'settings/'+name if not isfile(filename): @@ -201,8 +199,6 @@ def take_photo(file): model=load_str('model') - - shutter = str(load_int('cam_shutter')) saturation = load_str('cam_saturation') contrast = load_str('cam_contrast') @@ -235,6 +231,38 @@ def take_photo(file): system(cmd) return cmd +def take_photo_raw(): + from os import system + model=load_str('model') + + shutter = str(load_int('cam_shutter')) + saturation = load_str('cam_saturation') + contrast = load_str('cam_contrast') + awbg_red = load_str('cam_awbg_red') + awbg_blue = load_str('cam_awbg_blue') + gain = load_str('cam_gain') + quality = load_int('cam_jpeg_quality') + filepath = '/tmp/tmp.jpg' + timeout = load_str('cam_timeout') + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + AF = load_bool('cam_AFmode') + camera = load_str('camera') + + if camera == 'imx519' and AF == True: + autofocus = ' --autofocus ' + else: + autofocus = '' + + if camera == "usb_webcam": + cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath + else: + cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' + + system(cmd) + + def get_points(samples=1): from math import pi, sqrt, acos, atan2, cos, sin diff --git a/update/2024-11S/stable/fla.py b/update/2024-11S/stable/fla.py index be03235..71b5ccb 100644 --- a/update/2024-11S/stable/fla.py +++ b/update/2024-11S/stable/fla.py @@ -1,15 +1,14 @@ -from flask import Flask, make_response, jsonify, request, abort, redirect +from flask import Flask, request, redirect, send_file, send_from_directory from flask_restx import Resource, Api, Namespace from picamera2 import Picamera2 from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time -import shutil from OpenScan import load_int, load_float, load_bool, ringlight, motorrun import RPi.GPIO as GPIO from math import sqrt import os import math -from skimage import io, feature, color, transform +from skimage import feature, color, transform import numpy as np from scipy import ndimage import socket @@ -18,15 +17,18 @@ GPIO.setmode(GPIO.BCM) app = Flask(__name__) -api = Api(app) +api = Api(app, version='1.0', title='OpenScan API', description='API for OpenScan') +v1 = Namespace('v1', description='API v1') # Create a namespace for system operations system_ns = Namespace('system', description='System operations') camera_ns = Namespace('camera', description='Camera operations') motor_ns = Namespace('motor', description='Motor operations') -api.add_namespace(system_ns) -api.add_namespace(camera_ns) -api.add_namespace(motor_ns) + +api.add_namespace(v1, path='/v1') +api.add_namespace(system_ns, path='/v1/system') +api.add_namespace(camera_ns, path='/v1/camera') +api.add_namespace(motor_ns, path='/v1/motor') basedir = '/home/pi/OpenScan/' timer = time() @@ -81,6 +83,11 @@ def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilatio ################################################################################################################### +@system_ns.route('/status') +class Status(Resource): + def get(self): + '''Get system status''' + return {'message': 'System is running'}, 200 @system_ns.route('/shutdown') class Shutdown(Resource): @@ -107,8 +114,6 @@ def get(self): else: return redirect("http://" + hostname, code=302) -################################################################################################################### - @system_ns.route('/reboot') class Reboot(Resource): @system_ns.doc(params={'token': 'Reboot token for authentication'}) @@ -133,7 +138,20 @@ def get(self): return {'message': 'Rebooting'}, 200 else: return redirect("http://" + hostname, code=302) -################################################################################################################### + +@system_ns.route('/ringlight') +class Ringlight(Resource): + @system_ns.doc(params={'state': 'Ringlight state (0 or 1)'}) + def get(self): + '''Set ringlight state''' + state = int(request.args.get('state')) + if state == 0: + ringlight(1, False) + ringlight(2, False) + else: + ringlight(1, True) + ringlight(2, True) + return {'message': f'Ringlight set to {state}'}, 200 def plot_orb_keypoints(pil_image): downscale = 2 @@ -310,6 +328,48 @@ def get(self): return {'message': 'Photo taken and processed successfully'}, 200 +@camera_ns.route('/picam2_take_photo_raw') +class TakePhotoRaw(Resource): + def get(self): + '''Take a photo and return it raw''' + starttime = time() + + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + img = picam2.capture_image() + + if cam_mode != 1: + img = img.convert('RGB') + w, h = img.size + + if cropx != 0 or cropy != 0: + img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) + + if rotation == 90: + img = img.transpose(Image.ROTATE_90) + elif rotation == 180: + img = img.transpose(Image.ROTATE_180) + elif rotation == 270: + img = img.transpose(Image.ROTATE_270) + + # Create a temporary file + + temp_filename = "/tmp/raw.jpg" + img.save(temp_filename, format='JPEG', quality=load_int('cam_jpeg_quality')) + + # Send the file and ensure it's deleted after sending + @after_this_request + def remove_file(response): + os.remove(temp_filename) + return response + + return send_file( + temp_filename, + mimetype='image/jpeg', + as_attachment=False + ) + @camera_ns.route('/picam2_focus') class picam2_focus(Resource): def get(self): @@ -431,3 +491,4 @@ def favicon(): if __name__ == '__main__': # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) + diff --git a/update/2024-11S/stable/flows.json.tmpl b/update/2024-11S/stable/flows.json.tmpl index 9c9d94a..771e84d 100644 --- a/update/2024-11S/stable/flows.json.tmpl +++ b/update/2024-11S/stable/flows.json.tmpl @@ -770,7 +770,7 @@ "type": "python3-function", "z": "e6f4d02efb300ea9", "name": "CAM init", - "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", "outputs": 1, "x": 260, "y": 180, @@ -830,7 +830,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -1401,7 +1401,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "shutter", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/v1/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", "outputs": 1, "x": 510, "y": 200, @@ -1563,7 +1563,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/camera/picam2_take_photo')\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/v1/camera/picam2_take_photo')\n\nreturn msg\n", "outputs": 1, "x": 450, "y": 800, @@ -2142,7 +2142,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -2457,7 +2457,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "focus", - "func": "from OpenScan import camera\ncamera('/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "func": "from OpenScan import camera\ncamera('/v1/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", "outputs": 1, "x": 1290, "y": 60, @@ -7598,7 +7598,7 @@ "type": "python3-function", "z": "e43a27722b508115", "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_contrast?contrast=\" + str(msg['payload']))", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, "x": 660, "y": 2720, @@ -7611,7 +7611,7 @@ "type": "python3-function", "z": "e43a27722b508115", "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_saturation?saturation=\" + str(msg['payload']))", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, "x": 660, "y": 2680, diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index e632f87..39162ba 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -3,7 +3,7 @@ "1": { "src": "stable/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 15180 + "filesize": 16998 }, "2": { "src": "stable/OpenScan.py", @@ -18,7 +18,7 @@ "4": { "src": "stable/flows.json.tmpl", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", - "filesize": 325787 + "filesize": 325835 }, "5": { "src": "stable/settings.js", From a0174fd358189b38bf8ed1f51f68871c57769de3 Mon Sep 17 00:00:00 2001 From: Stealth Date: Mon, 23 Sep 2024 22:45:05 +0200 Subject: [PATCH 07/38] remove old code --- update/2024-11S/stable/OpenScan.py | 32 ------------------------------ update/2024-11S/stable/fla.py | 3 +-- update/2024-11S/update.json | 4 ++-- 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/update/2024-11S/stable/OpenScan.py b/update/2024-11S/stable/OpenScan.py index 9394eef..e634511 100644 --- a/update/2024-11S/stable/OpenScan.py +++ b/update/2024-11S/stable/OpenScan.py @@ -231,38 +231,6 @@ def take_photo(file): system(cmd) return cmd -def take_photo_raw(): - from os import system - model=load_str('model') - - shutter = str(load_int('cam_shutter')) - saturation = load_str('cam_saturation') - contrast = load_str('cam_contrast') - awbg_red = load_str('cam_awbg_red') - awbg_blue = load_str('cam_awbg_blue') - gain = load_str('cam_gain') - quality = load_int('cam_jpeg_quality') - filepath = '/tmp/tmp.jpg' - timeout = load_str('cam_timeout') - cropx = load_int('cam_cropx')/200 - cropy = load_int('cam_cropy')/200 - rotation = load_int('cam_rotation') - AF = load_bool('cam_AFmode') - camera = load_str('camera') - - if camera == 'imx519' and AF == True: - autofocus = ' --autofocus ' - else: - autofocus = '' - - if camera == "usb_webcam": - cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath - else: - cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' - - system(cmd) - - def get_points(samples=1): from math import pi, sqrt, acos, atan2, cos, sin diff --git a/update/2024-11S/stable/fla.py b/update/2024-11S/stable/fla.py index 71b5ccb..3578c4b 100644 --- a/update/2024-11S/stable/fla.py +++ b/update/2024-11S/stable/fla.py @@ -253,7 +253,6 @@ def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: return result -################################################################################################################### @camera_ns.route('/picam2_init') class CameraInit(Resource): def get(self): @@ -359,7 +358,7 @@ def get(self): img.save(temp_filename, format='JPEG', quality=load_int('cam_jpeg_quality')) # Send the file and ensure it's deleted after sending - @after_this_request + @after_request def remove_file(response): os.remove(temp_filename) return response diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index 39162ba..484699a 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -3,12 +3,12 @@ "1": { "src": "stable/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 16998 + "filesize": 16877 }, "2": { "src": "stable/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10253 + "filesize": 10249 }, "3": { "src": "stable/config.txt", From 5f1ffbcaadf12a87de94074e78db91f3cac5e019 Mon Sep 17 00:00:00 2001 From: Stealth Date: Tue, 24 Sep 2024 00:54:06 +0200 Subject: [PATCH 08/38] change on shutdown --- update/2024-11S/stable/fla.py | 30 ++++++++++++++++++++++++-- update/2024-11S/stable/flows.json.tmpl | 8 +++---- update/2024-11S/update.json | 4 ++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/update/2024-11S/stable/fla.py b/update/2024-11S/stable/fla.py index 3578c4b..026867e 100644 --- a/update/2024-11S/stable/fla.py +++ b/update/2024-11S/stable/fla.py @@ -86,8 +86,34 @@ def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilatio @system_ns.route('/status') class Status(Resource): def get(self): - '''Get system status''' - return {'message': 'System is running'}, 200 + ''' + Get system status + ''' + import os + import json + from time import time + + if os.path.exists('/tmp/status.json'): + try: + with open('/tmp/status.json', 'r') as status_file: + status = json.load(status_file) + + elapsed_time = time() - status['start_time'] + estimated_total_time = (elapsed_time / status['current_photo']) * status['total_photos'] + time_remaining = max(0, estimated_total_time - elapsed_time) + + status.update({ + "status": "running", + "elapsed_time": int(elapsed_time), + "estimated_total_time": int(estimated_total_time), + "time_remaining": int(time_remaining) + }) + + return status, 200 + except Exception as e: + return {"error": f"Error reading status file: {str(e)}"}, 500 + else: + return {"status": "idle"}, 200 @system_ns.route('/shutdown') class Shutdown(Resource): diff --git a/update/2024-11S/stable/flows.json.tmpl b/update/2024-11S/stable/flows.json.tmpl index 771e84d..fbbb52b 100644 --- a/update/2024-11S/stable/flows.json.tmpl +++ b/update/2024-11S/stable/flows.json.tmpl @@ -830,7 +830,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -2057,8 +2057,8 @@ "b33d604c.5f1a6", "c8b93b42c720b9cf" ], - "x": 525, - "y": 1040, + "x": 535, + "y": 1060, "wires": [] }, { @@ -2142,7 +2142,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\nimport os\n\ntry:\n os.remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index 484699a..9c34904 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -3,7 +3,7 @@ "1": { "src": "stable/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 16877 + "filesize": 17869 }, "2": { "src": "stable/OpenScan.py", @@ -18,7 +18,7 @@ "4": { "src": "stable/flows.json.tmpl", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", - "filesize": 325835 + "filesize": 327814 }, "5": { "src": "stable/settings.js", From 6c028550c5074ddfa56368a25bdf13800d57a053 Mon Sep 17 00:00:00 2001 From: Stealth Date: Tue, 24 Sep 2024 14:30:37 +0200 Subject: [PATCH 09/38] shutdown button --- update/2024-11S/stable/config.txt | 1 - .../2024-11S/stable/{flows.json.tmpl => flows.json} | 12 ++++++------ update/2024-11S/update.json | 8 ++++---- 3 files changed, 10 insertions(+), 11 deletions(-) rename update/2024-11S/stable/{flows.json.tmpl => flows.json} (99%) diff --git a/update/2024-11S/stable/config.txt b/update/2024-11S/stable/config.txt index cc525ae..4faf2c8 100755 --- a/update/2024-11S/stable/config.txt +++ b/update/2024-11S/stable/config.txt @@ -80,6 +80,5 @@ arm_boost=1 [all] camera_auto_detect=0 gpu_mem=256 -dtoverlay=vc4-fkms-v3d dtoverlay=imx519 #dtoverlay=imx519,media-controller=1 diff --git a/update/2024-11S/stable/flows.json.tmpl b/update/2024-11S/stable/flows.json similarity index 99% rename from update/2024-11S/stable/flows.json.tmpl rename to update/2024-11S/stable/flows.json index fbbb52b..3556333 100644 --- a/update/2024-11S/stable/flows.json.tmpl +++ b/update/2024-11S/stable/flows.json @@ -830,7 +830,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -8496,7 +8496,7 @@ [] ] }, -{ + { "id": "c59e7b205d80fe0a", "type": "ui_button", "z": "e43a27722b508115", @@ -8524,7 +8524,7 @@ ] ] }, -{ + { "id": "2afb6a45c73fa244", "type": "link in", "z": "e43a27722b508115", @@ -8774,11 +8774,11 @@ "value": "beta", "type": "str" }, - { + { "label": "meanwhile", "value": "meanwhile", "type": "str" - } + } ], "payload": "", "topic": "topic", @@ -9375,4 +9375,4 @@ [] ] } -] +] \ No newline at end of file diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index 9c34904..bd274b0 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -13,12 +13,12 @@ "3": { "src": "stable/config.txt", "dst": "/boot/config.txt", - "filesize": 2185 + "filesize": 2162 }, "4": { - "src": "stable/flows.json.tmpl", - "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", - "filesize": 327814 + "src": "stable/flows.json", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", + "filesize": 327504 }, "5": { "src": "stable/settings.js", From ae9cc6aa8f8c7c4765199b9532642c433de57d99 Mon Sep 17 00:00:00 2001 From: Stealth Date: Tue, 24 Sep 2024 16:16:20 +0200 Subject: [PATCH 10/38] add API documentation usage --- docs/ADR/API_definition.md | 78 +++++++++++++++++++++++++++++++ docs/img/Openscan Api Camera.png | Bin 0 -> 325918 bytes docs/img/Openscan Api Motor.png | Bin 0 -> 241864 bytes docs/img/Openscan Api System.png | Bin 0 -> 194648 bytes 4 files changed, 78 insertions(+) create mode 100644 docs/ADR/API_definition.md create mode 100644 docs/img/Openscan Api Camera.png create mode 100644 docs/img/Openscan Api Motor.png create mode 100644 docs/img/Openscan Api System.png diff --git a/docs/ADR/API_definition.md b/docs/ADR/API_definition.md new file mode 100644 index 0000000..858cd41 --- /dev/null +++ b/docs/ADR/API_definition.md @@ -0,0 +1,78 @@ +# ADR: Design and Implementation of OpenScan API + +## Status + +Accepted + +## Context + +The OpenScan Meanwhile project requires a robust API to facilitate communication between the front-end interface and the underlying hardware components, including the camera and motors. The API must support various operations such as taking photos, controlling the camera settings, managing system states (shutdown and reboot), and operating motors, as well as feedback to the user. + +## Decision + +We will implement a RESTful API using Flask and Flask-RESTX, structured to provide clear and organized access to system functionalities. The API will be versioned and will include the following namespaces: + +1. **System Operations (`/v1/system`)**: + - **GET `/status`**: Retrieve the current status of the system, including elapsed time and estimated time remaining for ongoing operations. + - **GET `/shutdown`**: Initiate a shutdown of the Raspberry Pi. + - **GET `/reboot`**: Initiate a reboot of the Raspberry Pi. + - **GET `/ringlight`**: Control the state of the ringlight (on/off). + +![System](../img/Openscan%20Api%20System.png) + +2. **Camera Operations (`/v1/camera`)**: + - **GET `/picam2_init`**: Initialize the camera and set up configurations. + - **GET `/picam2_take_photo`**: Capture a photo, process it, and save it to a specified location. + - **GET `/picam2_take_photo_raw`**: Capture a photo and return it in raw format. + - **GET `/picam2_focus`**: Set the focus of the camera to a specified position. + - **GET `/picam2_af`**: Trigger auto-focus functionality. + - **GET `/picam2_exposure`**: Set the camera's exposure time. + - **GET `/picam2_contrast`**: Adjust the camera's contrast settings. + - **GET `/picam2_saturation`**: Adjust the camera's saturation settings. + - **GET `/picam2_switch_mode`**: Switch between different camera modes. + - **GET `/picam2_show_mode`**: Retrieve the current camera mode. + +![Camera](../img/Openscan%20Api%20Camera.png) + +3. **Motor Operations (`/v1/motor`)**: + - **GET `/motor_run`**: Control a specified motor, allowing for angle adjustments and endstop configurations. + +![Motor](../img/Openscan%20Api%20Motor.png) +## Consequences + +### Positive + +1. **Modular Design**: The API is organized into namespaces, making it easy to understand and extend in the future. +2. **Versioning**: The API is versioned (`/v1`), allowing for backward compatibility as new features are added in future versions. +3. **Clear Documentation**: Each endpoint is documented with parameters and expected responses, facilitating easier integration and usage. + + +### Negative + +1. **Complexity**: The introduction of multiple namespaces and endpoints may increase the complexity of the codebase. +2. **Error Handling**: While basic error handling is implemented, more comprehensive error management may be required as the API evolves. +3. **Performance**: Depending on the implementation of the underlying hardware interactions, performance may vary, especially during high-load operations. + +## Implementation + +The API is implemented using Flask and Flask-RESTX, with the following key components: + +- **Flask**: A lightweight WSGI web application framework for Python. +- **Flask-RESTX**: An extension for Flask that simplifies the creation of RESTful APIs. +- **Picamera2**: A library for controlling the Raspberry Pi camera. +- **GPIO**: A library for controlling the Raspberry Pi's GPIO pins. + +### Example Endpoint + +**GET `/v1/system/shutdown`** + +- **Description**: Initiates a shutdown of the Raspberry Pi. +- **Response**: + - **200 OK**: If the shutdown is initiated successfully. + - **500 Internal Server Error**: If an error occurs while processing the request. + +## Notes + +- Consider implementing more robust logging and monitoring for the API to track usage and errors. +- Future versions of the API may include additional features such as user authentication, more granular control over camera settings, and enhanced error reporting. +- The API should be designed to be scalable and can be easily integrated with other systems and services. \ No newline at end of file diff --git a/docs/img/Openscan Api Camera.png b/docs/img/Openscan Api Camera.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb9af2ab5ecbbb9bad7af5a270c20415c2b562b GIT binary patch literal 325918 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

4W=w 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 z z+*IaP7g%lm4<7kT+Kst$5|QK*C7|(!F)RV07w-56kLZ!2aI?Kny9i7J$z-=?`?o#z zd%vDh2BNS#P9?unB>Inqd(Hx&GKSjXf22~`KoH8H9P9l@+o=SIBHMeqJO1d+7lFti zyRd@cPr{1V27tVtNlje&qc>j%(wd86=sz-<<+Omj_;}d=$TH}jJ|iCax#NGEGyKD{ zo;L*K^?cFB`ajL+e`v&iTyp*$kgn*CJfrzNb^O`Ada6fO0B5vF7}PEq(qBOrZuiuFui&x$M@3#s||xF=Vpg2pWWT@ zRr>p;TQm04HKVu*M#O(8%fBzf|Daw8r%oSIrNZTc0jsY6%c@svh9eDijiIMxx38AC zPsmXuuo(JKr|aqIDRWQS%gS`xl3qUJ z{*BN2k4v=SFIfx$CpkDcX!p_>3U>shaTSTl$*6MP1H{W`dd-e+WHlJQh`u31)86nW zQgK_MDjBGhGktCs+K-l${ka+e5dr!Vp4;+uWdH~U+8UE6x$o%9wGRrdTGcwQrX{oU z5||182VeDC^{TXbu3t!^y0YHl=l7j?;8(wrqY7@r0U*dwy*Xa;%Fdb10qw}wSJDq8 zPnAHJ2E^h^>lyb=|H$kOTg>%r7Cj|rC|zJ@2Sq}}mVj@dmjd-_*G;&7w+l^97aJ&v z&ueA*s1+EqS3T%t5cA&2_H(Y+iNq@&sQj~`f1vzb^e68YU6+Y}d)RbCOKvv9Lb6^x zO4NVum?8a#9p4-f9CEDGlL21SXnOSvgaKUY?p?K+S+jwX5i@@1<6kLtjc#G1t?Zxd zn6A4gF$_^nyGq5YGQ6LYA+9jLXFq@bT9rg&AM0mX zqwI33i`5DzUe7W9!J68?eHj6&Y3qit;#2g z(Iwe-!A&Hi69^Me_`4@h8`bsikYD_fjq7SV2~pZ{OHu(pptId4|>T#fXIS zhQn#N{ipZC8$&Firh08VC2U5z9C_M2O`uN&R*N5^V!&TO**tru z47}96Y+F|4`lV7pS*TJdXutef$Mp$@kM4i-ib@_kPG2n2k5M57ZOTW)#`@^H#5jOo zP~5r1Z+iFl?2)rBkW1VDi>mJHrCY!`S3flF3^aPrz+KE`S^$#s`SWY}6OfEJ94CMD z|7BPHn~nRqZOfVR6}_O7}+m^N;y@N$71>C?*50{XWCzb4L&FwOe`6 z4bsn!aN{d{CWkBjaN!&L$h`|s!~FA;+xNJKEwFLw#^Hk0;$6cVa$04N{$fAu20qwy zxRoHhhqr(8&tJfWqf$V`>@1gQe`oT&;KyIH2IA2oJ!1cOYk2Yc#9`YLEE?OP?26Y` zCAxGX=X1?(Xn+l`)8RzdKi}pQFM!skeZF}3wnn*0)$!c1R98jF-#p3x^P$lle#&fS zf!>W9;*&cif4z8p5;Xrd>V9l1{qsuoME(63Mu;f`7vd!3^?f$F-9f&Q)Z=_%|G0*G zz-T1(3nos{2n=la{^N%_*v)oc$7o~SR31J&-bl#({{6d{GrFQ;qhs)C)|iJz zYnJk3|M0O(DuFsQBU4y5hdTv)LhrZ{=kdTe!Y6XWb{SL^?!Oo zoK-n2|G(Sn)mnm2S`LnuQu?RgLL3ytI?*x#n9RfG|2$*6HSbyb7vj#t8Xix|8i!>4 z*3{6>PGjqu|L8f>9S(_nMTaFr4}YW3*!WLK3rMNh+hL|*)#>*fFND`x-IVtte(skAphi0c(Vqgsyl?6VQ+~Nw5#gs9YyAHdulAV4#&&FGNL@l?Fist z{n8k*OCj*iWmpcSLw|R(N^%!5g;@1?EX!1(`~j9qib}Z;@jCwWC|lFJaZcp zl9o0}UmjNf^y$-*Jm25EivQo`?x)t_i!e~oQLAh=>2(5fh6kboBzn#nr2jZ%tLIwf zN&x~yBzU504SnuOcP`VV-G-+HhzBZ4WevCQ-BZ8pOnb>C>GstE+7e>?q~!5~!-=Zi zq*pEE1cae!|35YeZr|t7h%l6CRiF{*BFaAUm{|G`@kvz94V+15qES)?4XfGboc85f zH!V%8)WjzL``#PyI)ITHljV?X>9y*o(eWBTK7H2Aym>2$Un9!0C~^8}9B+hUnebJu z3BucJoCpr?OBHR2?Hc>rz4_924z3z+aZvrXJ0&B!REC!_ka=tS@LQL-?0QVR@8$t*(4_e+9q zb!MD&sAHBPEc}}BK16d?-{%=OIAAbB-K&fnQUwJ&ysl;vwsg||;L8`*r7c>m|7umW z1!Tf9Bo(2qr6n#114LnyNOobid9MWp^`pCEOB0Qzr`T@ayzwYOQa!*<;@A0J%6kaR z>_p;C{C`o>|I9mF{pE(sA^m;*pX^h=L$7K!Og&Dyy)B?#b=hrCyKyQ|Ww^_R&Vrl! zfo6|I#&reQqj=rTb-L1om0p_afbC>`L=&Tj12&HtqLU)2HQ_ghG%bc=i#*8Ql}kHn znyE`xo*EjFmnJ5fkl{#a;0I4Q|FXg*R@D2qR~@8jf7euW(6NyX%qY&GQ(gNL1PLu3N$&0~U_gZYNRQo;dpclXokZ`WGy;I@9 z<(@6fYZ{5^P!uir26f31uY zweS%9Sny!Bo-}dQkmCXF6x8r7kf5kPd-f@?W|t*d(mfyBF<68w_gdV0sS~Wd(Cg+1 zH0ia>hVY-goT6p@x2wC`(j%?bu?nWr7{RtFErV>i6PUeqNqqn~ok;xH&cHn#j-G$iVA;YEm*L zh!9Mjc>n%>Pvxda z%^3-F>Mr0Mc>L+TdJAY1Kit$+1S`}EE?RyPeL8Uak2OFJeB_e#{dqc;{pMTCNH!s- zFi}xcVtwG@QRO)Y5yOKPPGS)hV{qrj=wM zR`)MVpc<>VX%@#0WEvMQRJr0YolU!Si@Zs=O(0p^5-%9oiMDb-GF2yRXJ*;rqAIr` z_osa&SY!1drd6bWYLB`iWPg7r)l&d?sG9U?M6;b3sXd_dyTBwtb(XeY984~H2p&%S zztBa1Xr*1Qu)nQWCp$FY{nOCG@6|>?Rbqi~Z#N+O0YNHp8i@51-rOnTI}FLNv#u)12JO{|Ic>kL%{Y|CAYv8xZbaX2 z3<~P?i22&V3_{QUZ4mXSav$h;8Ic8u4yuuVUmv`^@xn$WsHp7=`_C;`yzTb$U+?w0e>N^x@fg5J)a(`}CyUN??s{9Al+Stsan$E9U2E~I zY|_JPBZXHvneayFVR7+jfz0Joxx*qW>l2MsfJl6`mR1@qS5NU}=ky8s5MRGsXATol z^jn;B<+!lF*acT^eQ1f?jP@y&v&?()uy#Mp`AZP3PJZ9A@Ib@9JIN;2bznmR%Cf5fQRui|6{*tHF)~h6k6W4)*fpsS7}PJ?d(9Fm|VA@?iS+ zB74W72LrdRIAu@I;F@@m0TF!e!+C<)F`b+Vn$+rIA~%c&`m(=;+!H`MCwr zo1EOgLzp|lOKW>+fTJAxbL|)G-^6g&rLS471EX&Od%F%q zC)0h3;q$0cbYqT9Pq9c_^a0#Vvh^Juispe&!&k4oJv4{U@c^@tn2T3)V^l;cFQ~!i z8VtB@{q0TVEeqZLHuAlhx3M_&aR#T@Q)$Gq6IRvZ_Ye0h+xx_ctvf-xuH~uv5I4S_ z%kxQBMwY%x>c8iPzkcueMztQ%45Z7a{gEgxiEo&(Pgz+v#KnmHniMEBnCEXX@dGq1je3Fq=gAa;$RnNr0<9O+&FI9-JC<=kd7f%Zr zb>}GLAX4)I*2`%)5@RsnR=D5YSdBI=HwZh!IknidT<82*6?>;hKCQ2_061GJTHg#m z2Yfu#&}Fn=FgETKCgl)Hgn>*#nB4R3 z|Gc2$>x~u{n=Gd{lu;OZ(3vRA?iTfL#&o5bQhR%p)vN}uEoZJAutQxct4t8KtWvbc zq(CwDyM;&a`^VDH$sJgk-o=GA-_89OTl$h)waKfh_wGTUypp#m``>b<8(;Wv4`eFI z)Y4)m^V_KfFhIx78yLrZS-<%09`_wlxdc|hiM84XR|?uCq03^o3<@2xTWRK9{PoY) z5B(mT_H4IPx$nM3AE-Eveo8|MHLfiNHnTRO-w$*0Ze(+#Zx$W2og6%5$JjpoLoA0D z`zb|^kpdv&LP?}!{D zaK$EULMGq0zl7|`chS5Zeo)YD&KK?WK54uZM)77%p~XHu-&eUFM=wcRl8@U1j*ZO;#d`t z8R7oXq~AT!sxA7f4#FJ*(yK*;&zC$KEl000#Hv0@-+OgK z8Y1VAn2;cFOFhD4?`P10hw3G%W)};bzm2@c2O+9Ad1ICda!|#q%NI0lsz1uH5*QPK zg&JTqwZ-zQI1UCT@NJOWv!@^{(jRVzPDoF4^XaNcO7jgPgXBX!9=GS6fRM1=)eaNG ztT)ZJ*IaHr{`frBOi?l9k^$mQ@1J1+X$uUwxgYWhoc29WhDO!sP{BaEx&SzI$axcS z#GlD-1-0_?(v=cUn*ZWV7Xtfsf4;tFz`8LL1TVOqe9<=5(|c+hon0J!Xs>)BSRh;u zMPi`cNf+;KObd^sjzYZs5J0iPguaa)a%vn3_cs2>wp95`_%w$DzGwgbkVCmg^l=uK zVamc3SsH)j@!UPS6lOzU^iv4vd1|P3Td-yJe)|{N5I}mIh*n7KJDXt#2iMTDEC z;{O}aT1GDfL(AZ{HL%Bd_5jLhSE5jW5>T${rG2ny!_ZBXfa~q2hK4`-Sthi(E$G-m zxet$<%+LW(y8@-`kJuErx%lk;TSfWhae zC2#Bj#dEo5Rc=lmhqR=4Te8C}i0xB|VM^hgcTry!Wrqv7GCx-qHl?FLdkQtD&6Q)D z-k#%a+ZBi-VR-T!^VYr|OZ#kqPO%`W@g^rHuXPF*qo({vtGUU=)=0ElF)N;o&Eg5( zR^Q#?oOUVGLM3GC-0jsMm~@zg=4Gj#VPVp%f(JLd5Xy$AMZ@a?dR0;54ls_|-;rt7 zQ>=pfUa;?d0vm_{t6&z-fqrA?P$^WE3;XluT?vTn6^_$G_lqEe9D1Ef#egZkYClq2 zosOJvqvL$&88$zB|2ZT#tae5PICkGO5ZOv=MmdMDJGb8FJnDT2zvk3GjmR^loURXO znlh0KY-=7G3_rtp1&%_+$Zt<76^k?l*{4y6p3`Mr!fcHl%2-_rX`&{RwY^fr?=wy@ zWAUR7bldZX2GM<9OU9?NX@FvTtiH9*d)GE1XZnqB^Hx#2o;EEu)z6-r|1nyJvln8& zrb5L}x4FwON;&yhYkGit($E-~- za*U+OP#*dPEqD1f6>O*<#dO0nXj=u7h3pswAaM`hSW2ey6p`^nvRO4eWvZ87-eCX- zO6i_=oUodE3<`1<|VR=K*)*VdtpXu?o-+QtHVt>kCiy|B1t zbD55$xYkDTO2rC;$e1tZTw)#y#gGQ`;;u1iYj3))qGs^6-2t3mY)5I`d)J8~X+Jx9 z-le)a&wQjIiV~k4{azp)#JSmyUzm25@P~^9FvBg=?ksy?i<;%chO$1NdnWh_Z@dM5!s4 z_)R{yUfZWKHWD1|hbKB~NkYwpMYNR{$TyYfL| zig=F6f*UcCscrX&2~1h&w@fM=*iy-w`~dcSRS>|wdmrd)IXH#~?*B;i3$gV7%jAKX zV1z3ERDkPu4wvD_lY@>^Yfn!^^L@=HshF1fb$;NtTlN%Ikwr2kqs8?F$ZT3A#-VX0 z;u{~G7&JQ0-Tfv>pDt7afu=e59viwL2W#YoK7 zrvK^>b`4U!sjv0$x75AP_+a(srqJnXV!}f+%s%t(pVlA!2gK*@Zm&uxN+q${r1#7l z5ez%xw4MQ49r+MSx_oBCi?7~m;U+ZA9A`ewwCRn*S>hRm<%K}hsKijdsecza4$nyt z00}8+qJqty9=N{6Rc@IHy-BJ%+~3hnkJm?>*xRIjWf-}L(UAk0VAue{L|oWo!e8FE zVart^E027d(iuoSw!Nb@>t@&g}Z`bXv^%Ro=hMogk@JI3yNLV z&XudVEh!f?ZabG9H5r(OP%y1X+5PyU_2IGcxRju|&A{m+QnN`}yxyyrgPk19Zg0X+ za!zw=$Pq-fCqdNtjRii=qMTlWc%?eIx0dkCFN3uEsnX_+-5 zdTvSGi}~BD<9EG}2R4noqoIDG+(9k*n-ozp8M{BZ#jc>wj1Pv%h4Hb=GB+G8%OCGtI0wy7gDPCTzIw zpX#s^G!Tz%)m~*5zbN0+-4j=-d%-20VxApHHQYZkKUsAW60qdX-KT0)?a(*O{_Pr%KcvYF)L*kabK3g}0Vh~2kl)NLOnXQ+x6uFNy zkR*APk*y&ot0_Cv0?lXK6u3M!E3Q^CJ*8u}_~Ftg{k(w9pr1HPezCJfu`JDFg#kr* z(?;2v3$n9|xWC5y<}yg#i~wJ`oi$X3L-m@j&!Ul}N+<7dGxwP<+FyPG z>SU{_z2S?P3**aHTYme^)InBpF52t7mktay-utiPLLVOMZAkz`|6^#Jxus={m;+Yo~}|CgMdWdY-;3G+YTJ2GZ6rJzK$i z7Qtx;vmp+ln63k(tFB|yXpKVa*%-bN@cXt)X6iB_giN>UyZxPFSne<~dem9pXF^%{ zkN)VXB+IyoX2zSXh{xNKos}r!j{OeB&UZ%&GoefhfKg>(5wNLMoZ%x^OuT|Y5oR#B zIDV++#ss|S_=}U3O9ON6#Tq&FM#RKp^zsIi0TW~%(p?Mtx`RpYRX}G;K2=8u3`dMP z=70t#g*xl#JvlvXOKebMvBs5g%12dbH_SQLtoEty`lE(OA15Bp$w)+SL5mD2p2MWG zBA-@xD*N&q#>BoB;anE_zoh!@jxL2sn^wbqJ+&t^IF>ua8=|8MiOyqn$pXEs0v{52 zV`Rtkw3oW0{$N2^pzs;5F7$3O&hSkFX2#01yfc5O;9>DGnx-d_T?do$On$vf(lqkM z#{%NeqXSClp6xf(E-hCpw8jK!DR`3=NYADfGAaCYIBMHy-ANeYeU?#FsBC8fz*GvKb;xEe})t=Q1-G6V3eNJUv6{1ED=hWU`Vf_-h7Y%Y=YAd6YQZ zHb0){mUH(?%B$>mRR(gy+3lk4{Ws_QJ?xdYRfP_Wsj*SO5VgN;Q|syKwE64$dh%!D z!PpY&QbpaS>#T|VMu}4QYVkXe_V$&^{kcdd>f&@@j><@y1fCgEFS;4Q-kyX68F?CW zlvEkZaDVw|4#G3opp;sZ=!zZs-7a*HA@cgm?gnhWWQi5UJrJ7nF)mz@ zqKsesdo6dw`G3>YT_p&C1yPq3zWg-i#t2tHlFOj>fEPNJ*Izi4@}~a;g#Do~GpS0B zUq5Cf7-SOH)i+%m>m*sN3xh;?7TBzz+qo~3Xm)CM zdywkcn(50?^#^8tGj`gNxHLpKhX^yhw`pZ3lTJNy-1;+k>N_sMa8s5i8lLt3tr%4G zYx(8IPba%;wUl0PbUxDBB&PnlgUbx^GY^PvuKQ%rJGl6+ufnK^N#^?8vQqKNIbM3I z0EU@E%xpq*NSd5I^khZ!YH`Sz_$KFeoie9OkjJjxTDPd@dZA3vx7V+6#CJC`k|rnP z$`1D9kt9bWZ6bqRSHdRdjJCr~?N)b00Bl(={o&n_@ww7;k8da2)k{no%*nF8ao-S_HnChKu1CUa-hRptYUz%*pxMH zt%w;ya__m@(v<^M;Pn5wJJIhv^_?Xqam-u$43pF%(@lTgNC?`ZP(O;C{o(;r{~|PU zA}UkDu7;ePlkxtr;?`6#T`-Zsx4Re)`&2vgybn^S%k23?=OzApluW$qUCkiV3&0L*fuWPs8 z`9BDe9YBDSr7Wxc^d?dpv9kaA(Bc>Eov|xrTI+G68YMdTyh;9rlIfomkh-=#*K#>lTK|3MibP`zfC6M{ zqr$2-sUHDoqX|6^XQE*;(e6`W9w5zQHz8b$E?D7s#&r>VVAIHvm5A9{)Uj@+BQTVW zT;sP*5arW^wW)0jZLz1_c6P{D#TVNhE_K;U|HV?ja3TyANqHpzotsOzs)voA?q4;l(a{#!7beIbSSx$nrMeBPX*EYTX+#T z3Ot7Qi`lT_%l58v7H#9)3dgCK>>r{d4KRWm;H@uX0 z+~5JG{GO=I&Kj%s%+H|$oFvTTfmgxFn{^m@cv_DB<@T5^^xeg+lbmh-t}kCbsO+3M zBn9I!hnYHOb-}y3*YI*x>eo z-f)UWs`GGR+@^gxK4f2-ShRQfG~^7&IPNJO`c@0+dyxf)8Fb{&5$zcbpA75P5~sB> z9YLIvRj^^zr<7ORqzNvUVQkxvHwtln!p(~>ueSDB&MiRy0O&pWDH7K?4;#Qb(Yl8} z&Vw^*=v1Fb7-5R$G7m1&Zm$7It?AQ)>oEjc0|byhhl~qg#)R+ zQOm+3=H^mQN{oYl65WtWP^!r|S`pNj(zu&BWP(oqc9)yMx0kOP%*Z|Gf=*km?%GZqbeYS`Z(;vQ3GgcglKTLaB~~ zX%;tPF+OF?XJnQhp9~OrUam*tks0AagvQb~UKJ6R--}`kZO}qJ1Khw=e;hgyb?NhXFAK(@9?-i~SIMwP9e!Y1`==CM6MH<2sn~t1b$2NKK5hjbwuSze; zR+Q7^j49n?+WMY8s2wKW<0vu(Wq>L+r*6oijG^g@eHH4Dpg|Q90;2%zy6N0I0oxkj z-pJz=GMNq|5ag>K#xm6Zyp%0cTlMb)8f-+x`ZR9nK|4}+!gJYAMEP%3)#ruoS})yP z=wGmaOC(zQRcfZocin<5V4|K~=jBy`HP6udk)p>7!;8)rKQ4#8u__l>Y8#&VEtdDR zjmM>FXx2h z6Qm~9PtJzO)H-^W%2C$+)JwA&EumC(t##jZ2NPLvg{YJxR2zIA?i@TWE<-o96ozsQz<=)+>5G@jmrxAc{QACNn1s3P+)8zyT?sHP_?nY znVUT6aQE|4*fLolt1gAR?hO4VzaobRlndAk)UWf)4_!2C&Q~Y}wDs8(Gj*1rlZsuMNV3_e#xX6TM7j||u$d&%|67K;eSar0(cM9~J z&XWr;Ot0#R>^rZXTl)RjgK>)X8Dl|bR>!rM@{)7QN80$nD|qtUXo+>GGL`nFKn09U zIHjLV1X0bQB-ma2SiHod1usJ18E*aaHMh%fB-w;K0y_}R;5T0m?oIw8@n%8G22mc& zLc&A9vbcvKNk}P{$|EO#Q9o^SfM z#O;+G_Rb|KVYP^_EJ$85KeC;QFk41UnYNLfegy=-yh?K1-k=7Ft7SF z?i1^KG~Z?AFBdxH*8A>}y>pbYynr-AIJjJiZLh&o_io@GCG51{ z)_ZRm8Rd(nrIi&f*exEt5@$F3Qy1n_tjMtZ z6oh?d>;xmE{=q#em)AH`>gf8Myh4}Zt3Hd_7zW&cv!S5H98S#3o96AO)KH$04$F_4 zJJ`8AzRbLS?ANbw{{a2_pb0*R9_r@&^AeZjIrmj{vc+c*cd;&YhO+CROrypY_+3^$^#Qg8urx>-v&@8t` zxJdHF%}q^WHFmy?LDCn(9eCUVHNu$qmt34lg^!k?oYs=nkc>e9W0h>ENwdOe4BOsolVi4 zgau8KmZ^WJwh`60Bd5RKCm0$>MY3)UhLv~|{Q3gV)FQ#&zOpiIo^Xezo6lpWb$H!C zU_}Qx^*5^DS3zfG9rH`(4v0*M5~uC1+0e%f5rDdd|vIg+jjV=Ca$`A4MUUHR(Xt@{P+zp zA3S3=0K(tf>Xv0jNC@VUAuO?_m^Nd|@AAoOr}N#rgy5esR-Q>_d$Y7{0EEgLI5t7A zBfoRqZ@TaDgqIjEM@;|Q5BPD7paR3Z*QBj?>H!;+nyIZKax)p~}kc+<)d zXEE}^C^nKldTAI$2G;H*aFUIW$2g1kfv{hxA?8+T=g0GZ>HVlSt1bI9WKr3lLpXoQ zg*gmLrm2W7H_f&*S+~rB9=ZyL1jqvs|Gm+BmDjc@sZMx zpmHcWJPeF#v|?{{OEwF{FZtW}JBnzS@ z%~MXIXuDl-zoVh`0wY{|w>`0@@Ct{%GUGuBy&4j$-9?@9lBp5SPz(tNzf0t8mdr6N zpN)G``a8%vXBmTXlz$IJVeMTH!Bb+6{wO;0jm9UB>&qYN)y{unOof5-hX|B&mqfNt z5{tZuKz5KcUwa*egwTf)hyXp{vi5vGqHYPt5 zJTAOvr902W>3=N2Ktuiyuwuv&?HN!xn%vHcKjQ;wHYXwf(}9A@>=2@!Dcno@Lr%WG@6*(TABQ2Hq~UcF6Yzn zgWYmoCLAgN9v?UxVJ)`p#%7{KQ8dRip$&vb<=nEMT6M|)u6 z6MrUV1kZB8J}F}A+(A0;RAkAP-`b0X{#=-!bIi}k%~6qU!f9edH@B%BHGyfO!6rZc z9djc0v=&M2LCY-9zII|CBMralI!TO}wq^MUQQzGI0jYalkxkvqQS-w~z4{fV)jXBU zMOoI#D^1jl`K*5OR#M(i--f*Ii>_}}OQhuFT2JaMObrkqHf`5$88)TLB(L-`zpddW z4ar!JRZ_yM2zGH{#FQ&z#}4TGmgIBN5Rqc8hg&l^1Z2(Zn_JrAnrK0vQ|}gKT*krQGxt)tOTd z8zJ99Fo?G}0&$CspP8kPCa6Pmf7Ln;v{PqaX?;KMGEAfRj zTBm~09EW&2xE~YE_Yq$X^_?Qa${KI50*a+0|;v1ouU}$^ekA|?T9*M@X7g48@#QeAg(mMl-c1;yVjOS#Zk2*xzgHhBup&C+?vs22^zW$lsSLFidkXc_ z#}!2iMg2j18o@?OxkPH1QcJ77{n-YW2l#|hNbBToOVB=UvwDOrg*N5|2IIChx) z{!5mbFSuD$jsTFlH(0m&*Y1}2uaPAzWy0SBbD<5NW;_Y*^7d^j??~#|;du!YKEH5T z|H?ck4kNo={!42=B3B=3+C2KehR4e|C-|U^|;oNeu!Kj z;d*1@MVjZW#E&m^bC zh@cT={O@xvL0wN%k>BzqNd}att&2gRLsDC8BVp;oG#taD@agfKNbtUlU&usVpK*DP zG-QkU+qXfOuMSn{?hs!%Gxq}eNlwqDwfmIuMO4duQpvAl$~?L}r!V0jsG%YGLnp`( z2RF++{eo<{g5IeX*pf{?`)Dgi_R|GkzCA85pjDA8`AW#7`Z!a<+(CJo0+On)6OS zT@rX(zWj1!T3hOpj}7@&b?3>o5DXBsylbbPaYyuJPRV(I-f><1I+M9f+77r?q!D4e z+_}V8ak7=g=9n=iZM-#LcQ?gRMSJu@F|2Ya%FkTp#_tHu!}vBM37nl0iux zzwM1zXPSiRv&eb-2}6swBnG(8;OJOA+DZ+E^H-IaCcom zRy{qSTJC(M%k@29G#eA2DqhUX!?e}juddp0K;uqrg+=;~G_RbTxtJ`}FlSh`i^Mxi zX1gUTKDl~zt%c#uQqf8DhzrBYc$$SLS)cFbQnAR=9nWwMul~QN2Q*8eTgGLdW$>JA z;v+8M?2XYW=Om)+u#D?NGl+}5%RI5pytfaV8eencrlvP>#4;b@J_EXF@ex_O&i5je zT6QFLkQCIN^b4eErofwIR@br3z-7Xq_Ue>O0k!^tZgnhk>(Tx=ivnfP_xg(4*}ts) z7Ar5$^t!64{$VD(zoc6C!|Gv)-#!75JJ@%oaK}X?)S=Q08T(S4EMvWQ!Ea~AFzpAX zn-GYz2&fdDs9w3h@g>~l89FXiL}=(p2aW>>W(2dh1P zesAXq7HACE^hp?Zo2u-uaaXbwrtQyS|t2dYthr)^!V- zEIcxfem^KzUNxYFU14US8>WO+b?CLF@EpXax>GWkI3d!k1o7L2`fea!E|q_pv{f1Y zuM6`W5UVxfNP(|Wj>A}6*2|v#DStpGq1kmJzA%MEJcESb9uca)%W*m6LR5u zR_q2Vx&)mOziXQLF#$+?$%RCIFp5i<+uYeD!M~_dR8w0Aurid0ldauMI&c(cMuoRn z=KT86%=zX6iyI)J$~%Fiu*!igUz!Vo&<=y;c6HlO~)mRW+1XxNP`ypRAPCfU7X7Kb70L=WfPnm4(r?r&$#iV zzYnfi5_sV!+;|1UNnDg|G^Y63VocT+Q7cIcw`+g@Ubh`BBc>_=^GUq=Y(#s+X>n4_ z35d*i{R$5)V{!py&@pLxCjNY$Uu&mnzvncTNvbR;Aco86jyiK*((MkvdMWQSnzi@AcL@>_TCHU!qi`^->B$lwqk6&IMDOKVaAN-j<6Ga^TlxK$AWS!UpnKkOvElPm3 zJp9F_($?o&d%k9z)_|ka4x|)5M`I4XV-o?4cD-M%w~Er-nWCM28$Pl*my8BIZETFq zij~)pp1}1`{2hB&7jiP<6D|Gqceetb#>gyRgvS?|U+!MNTp{?Gy{bD6$w65}`xa#gvQSMFeK7nRwP31Q^#v`kMZCTv^YvNARL&R|e zG30H;PIe?R+Yhq@0Y!}P)1{s>p0X*{aI3wqp{x9H{0E z&ETN3Xxh`^pWlu?iEtYDYw_(>TEp43NY-F!iSl`oMt9|7{r;g5K`Ocj2(BEQ2( z_OIT0r<+?2F5;q*}q%ok_P6A@}MY!cM5-j(qliqBi8A!d)BCzizz`OX&W2>03>9`OOQS^jh2 zNyeLFj1=JbYctnnWbm~`mlt-D=AH!~j1|YhREvYnEsGf&?Jtu=zxre)0+M^v zt}P=n@&$y3_6o06GAMQ=59eC{^`_Xr%DYbiu&kK>qV^cP+Aq&!X|*He$@UX2sx~m(~(=p;nl8MH)h^w zJ)AMZ$13byPm`8=yRwMw_UorJ&`@3)0LA>yUb!&u9W4BXX~(^h9Gmbv7SA6gMsrDB z*SRTOqdig)<|F={V_0n4^Lt$Qqqb)K6oNc|npX&r-H2z0%yQ4(TWvvX{}P{G+%-%p z!TwrnJ)_{?QfJi+jxKLYL~h4Kb=a|3h5d_qo8@JNmdz6<*$#(E^^d?ksP4U$xLaDz zEby72Q$0F6KC#}+(e~?SAMTfjmA5`$mgR1n8hZ^=49KK=PM)DwJuf%0U?PzEF-69C zZ@WXgqz8<7VKe$ZCgIks?ZP4t2w~L(7ax%3qVhB3_{z>~EsIHBrBl4XTKgJn(|uo{ z@MzIQhjF_Ynj>q00MJ<5dyU;P39zHrj7#2#rsaYgoS5nOD4$nGUw7bc@A68d^Gqt1JE=mDjuo`kL#C=+e4cfh-aJt#NlG!6Ah^g^3>Z zrVJ}#kpXW)c^)f_i^=-;d|jqsvu)9yA>OAwF=2e^SO4oRK0X3 z=74f5>m=xe%CXP3$^NZl+RBizVRcuqe|s1OrX{V_q>2i$WbwJQW{C zTeQhxCd$TqwG=$s&ArK81O&JsN+WBu)d<(H|Ct#*l) zw++p`YYihLYs{1y5tWt!K$>Uydz|iZrlMa!70Zds3b3-nVW5PH& zQLC>|-lg;+oD;)e^X?Q&#B^sI#FQr(XyGWYAhhLFdDrX}72dRBQkBDK_m1qaO+VY- z?E%hHAhB)n_CRpOZ?YCskxzDI=Q7X)CLXE>gsN3Jl2z-*-C8TPZ#V}81x;;X=(wdP zfbBZCs5n4(D^Ex4qL`a+I`J&WH&^?7JPsWY2DdD&bNoV4Z(9O&@|r6)?eIG2VMvkz z#PpQq!oYBUw1bfezG7jjvGdomhUZ+2+xZR02Q@^U_juTnP0PeN9D1iy~?7}$q7)YNK5*e>{vKJb{| zj}x3PBBb83AzIw%8qV&T=M&fTt{lIz!D#842rj1FIIT0emQVK0ZOOOl`hu%$)ibI^ zcoEF7fT8T$3kQhl{c{peyV{$AUdgd;JBOBtS~X z%2T_0?UuX2s^AIAtqxrMVYob-b|sm<^O@_RvK>fY?1uHQ%9RnI6DI$zVbh$#cJcD? zlv&+|DlzCh7(1moUQs*d&(VEblQAr7+LZj83JsM8Hcn4=$ zW}{liWtGoPBF1Gaf(RnJbvae@r1bP(W)zbMsn#gN{HMAxQds5K}pn=ukZXh zQc4vR6$-bAfvjmxtPBZk2g&6R%LL zcZL+-G@5lgm`e6hf7B(%BYryleIwC`u#p{mK!fK^rM-K1J6>yY`+j+`NoCB2g=m06 zy{DNV;=tabRpG$w)}aIexUe1Oow;RI&`X6wZw|l~miJ`yFR9L#m0j_|>`QFYX!%{+ zu%TK1+vas2o)!eX`WtwsBv+Iu)Nh2^CZc$EeRspwK@w4YD4*N5XZ@~c zQ~a9>9Utl{7uyW-a+mmJOlkXW`Wt)v#^1Z(&kOz8uV?)1-b&wdz>{u&>SfwGah;{{ z5pfH|$x0$tlo1BmF2n|HuiO{YSI@UL^Q>Z=uAnJ;eMH}FsT%gHFfY52k|o6D4ROVXx;LZ1Mtv?5@|m6zSj zH@!NacGDxx?^{IsqYU{o0bOrIK4lVt0(|=9WjuZ`^0mn&T|q7&V4qD--T%1Y}zU(e}Q2o_$J8T+N=VN z(@WYnl;Lg7yc~D^X_eKs33=Bpoz5FWZjMRKrHXz*qz$Cy$65a>pOp8eq2RT;94W$C z-^7s9Uk|5edO6)E%*ogEFeO0Q4ia1|Iy}2utUP58$MNeM{QR9E)kN4Xp0uxsqt07?~ZUVdP z5n%Me%JVbo;u2Z(?*-ofL5bY2I7%9(OPtI%+i`^=% zcTLDg-_cI5Ym^t(-hD{DW~$|DEXaJQG9ufZlVG!c;1to%H^L>A%QcQL=@8)?X&?zt zo(Rt<83jdC^M4ATm6}5dLZ52?imU7Y!`XYsHJNn_+oM=fKt)8UDgx4xDjh{Yr1vI8 z>Ae#}6;V(T5D*YTQ<{{}dkBcA)PN9LAb|9e5R_^Nfp2G?=b3Z9Gv_@z&R?j%3CW$k z?|ZGa*SfCvGfK0Rk(&HX&)%c61Nz!qc{A7E-qVFYuDeg9#>9aG-G@m&g*?u=Eqb~B zNA@{Z)RkJzT6Fm0^HP^zF+Cb;gCiK^IpDQ?3ohbc68ZL%67%$IBMM9`8Vq;}Oq>+@ zoTHAinG70uX-_0Z&O8P-jC~PNkvVI9s6ml*Uam5`PV_?cX`Ye0;Gv&Cjn~dI%VWdc zIBJ;45p!KuOt?|~0IH{o32yYk!scM-&JL?J?|nAw!hR#TS?+tKzQQ>sk)dXgG0P14 zh@Z0fqIu%5`vW1uLyYC_w72t5&-?$~c>l=^pcvS8*PQ0;x3!fL)p&Tg4p@CSlnQZk z^rGV$or|Q@YK*4c`rg7z?=R2t*yMW8wKG&gh|k*YMKWDM78%NTs56~slg59S;}yoU zvTz^_E=~-qgtW|C1l-5}Bj#n64q$AY_@ulLY;o~)%RCSus<7d|zf%pIOH5YlGD0Dj zkao6P#Zn?Z^XQ2sUqVo6nh(KrLX3k{qS#mp;4q)|&35)kxogSw7cR5GZ*`pU24@qW zZS-BKetIVS7uDqqKN#F?dG8vsdWJGhvwCLQ_0$L%yfMhWi_QSY`RmrN8~67{u{QY}SybnM>+(XImRXgyi+ZBwj&=@}OSB(3~iPk>fLusUb zov(DCLI;gv!r?0^FNI0YpchgBKSlLLF-++krC}0WMUrkj&V2&Y{51ao+QmmzGN zN3jeFS*~2Y4_HF=BNQ!&{8eFGfrBlmEKQ_vb8=F?kX+D8IU%dEP9tzKTe(=@jJ-VaoIvhc>gz$-P3}U7fVR8?dsI@f>?(iJ9||J-gtX`6zsFdZ>fGd zn*JA3>R-$IRJdaK35n1ue%1N?SM;p~71Rfn>E9XisVxuP<{ecr`Q`QX*E+mtkTr4` zw#ol)lx4?EL^SN6fs58fLD)YQOuSGmKPgc<#wD_ib2Y7hHhe1V;|a%i#0L6=T)m}_ zt5I^lzCZu{^^giFFNqKW=M3Jnh-M$#!E&sF_lQJoBal`n8gmeW9KojMe zbbBnIA@XL91#&}6i|{TYb)15c1V<{?UYz;NE%wGyzazyUWjthdCERsLrvPKb2GYJ+ z`+)pFzZ`%!>F(autWq{2Q5Y^4r8_^$kZ>L34Oul&j%-^+8Cc_*~zaALd_ zP2*&=M!!Bz1+T8A#`WDbRfhkhj$S}OLqTg}rPDi#^83?#celpEDr&N`lbbI-*FE}5 zfiWK>QfB!6KYGmmXPs>MhY=O#+9~UMBfN5My0!Kxv$H>+giDrGl1^oV^2vWmJlyM` z#Lu=VJ9bWOsfDP(-xWhJdKQV}uw45Xu1JG+Kp6t8QU%EYmSHeP#1E>#M4wjFg$ozf zhAO)AG}XvuNI?dhmXgA=vyG=?n9Z+N>(Rmu-HxB3)rc>&Q`Ym2ZB6M_`1{NM zd^6}Y9nB&@O!9Zh3=M5?!XBD|(&1t#zj0uFCAU z=NRwZ12Q>gn3F=?#c$@VbM0xaGv>~LN1Tj?JQlPDYNtj7Tw9z>}7 zLn+tAt-eZ4Fr_R>IFh*XwSw7w2r5kwtR&&C)BJ5i|H4iAdHzFYJ^%17uq?eKvG|@^ z62qnj2k?GNvYd1GZ|x@NkQ&I?&0i=6{e8J8erG6C>hgbyVH>XWp}g)1U>v5!fAKbX zEmha}?&znTzyCi!Ut(o}hwxbyGVXNaUUJfbjqg;M4a+d&YB_&Y%1y3xo%*edQ2UVe zZj2aPsVKhzcrBhA_QsEX>kb|BkkvhA+q4aw#Ztr&@RsZUG}Hj!c3`sN<<{;RgSc?i z*5~uT!$UuC=syASKYsj!Z$$Af2B1-nfp^UBx-d$>^_uxz7sfddY?uG03*#fjg>m;cT^JuJE{t=(>B6W3 z+|8KZbzztQ7e?Ffx-g8v_44{%7e*9VoF>2N!kFgOnFa1WEv>tBPk&2G@jip&<*Qd! z>a<*W)}r_3E!IZM=zn>a?A+pBT~baEC_oHbJiw| zzjKxUeD_0dnC)qq8^{|^qnO-#BuDTOiQ^Y3NkCob|39L+jFCdHzvTF@u;|x+3j1;F z3imSuK@RT+pIoK(9z!HUZ~vuUjKdp|GW|JS1b_fOUyr`kKunN}-PQry{WyZR>yE-RTFlDm^C+*tgP zwD3eQ0J|*M#?qF-%Zr;@?m6*zxc&?uC(YC-yerUXc?Kht9$Idc_CBkma zholSOYMwdH%||)ACV>OvQtwI-&8OYwXuO!d{TtDDIBZdeqe|ZaRvB6aFf#Oh)J)@z5p}Vyz z@dHJPXSu9>&sBa<&+Ha z9k`spfI()it9xq;!}<)Li09<2FGIXh_^s~`@>iZbnNxl?)%G+X;n%}R z{qYDzOZ8jpL7Td+WnQa#A51rxm4mcQ+=~HLSy6FoA5`%Ye$Nu2_>NKr;p9}_5W2(w z0|VVnwB0|SI{o{`%sh8>sAWDjX$~+?7upL2E}LRV6iY;#=|xV{9B0!wN|M?j9zWga zClUGcY~g>DBTk*Yngl4zV}I^GKFPz{kxqxEg+Ohv-H)3~J-7e+q=flDf7k5{lvh<# zKPK*TVAbpZjih9$TjzE3-#*-bDiHtAe>iiQQnzFm=!ydsiYtf0qc6YAR79Go>D8*} zg;<#Ub5q-?Y)a))A1?LTICbzjzvAHhW%`PL{QSB2mhvjw<}U?IF{gVY4!pm%B2)Br1 z_&Z^arDzMN2L2!G8Ag~7Vbg?L=8;o_vn*!>K}vHAHI|G2d31w`E}su~j2+b6(r0?Epz=bt9h{;?oD z9-ry4j4A*>+~4`&HFE5usrA$c<@l7iCAVsDWTFQ~+`*~UPa}f=zN?=wf=>oC5hGyL zNvyX1sfpn9)FdAndUYEBhwK0up$YoW%oAs|K;kpixQL9UeAYIhKeX7xLcyyu67t{dLNv<7b5 z+vn|qfkMh;cqfIve&Iq&lg0SN;<*A#WU7ayF4sixLNI7>rT3s8z8jdx0w%=&JQ94L z;pqnQt01z|#qI6Md%OM9DK=PwCA`o+)n@FA$pciTNwymV;mvGw{4IkNr3lc>h* zkgDPEwaoprMa{Or+5F6qq$PAlypYE2Yt2KoliA)_ueRA?&Skr$r3>B*tdOn<8su7Jg_5bA|4YiaWaRJKv$6ydW;kNSC;cbQ+|I}T?E>{ zaga{-yz4|Viu&U(AM(7H=LBb7bV2p%dC%{98Wgfv@~F}BXI`1R!}Sd6jkL9oa#ib% z^E3CEjm|Z#@J&pK(?$?q8Sv_JLdc%CE$gI{cJfcu#2Tnqfk&+KL#@Y30{HRyCzWX3!^1%P(=1&8qLBD-fEJ=P_DYF z6b|1o;d@!d->*!HkFV>v$3AGWn^@9F6*Jr7&Y6Sai#P)NtG{C$tiXu-cJ*y_b#=!% zUx>n|d*^v_%o}{2Fg0%GAN3devZK;;NmWm~wl@w3xYuISB-6&hOo-I?oS9q@K~3<0 z_M+FT5wE7LYn%t2Q>5qAh+o6gwNHN2YJb5Ji0|{B(H!n@%1@t`kwnnVJUb1D5s(Yt ze&`TqV&{5jd!!aLmV!bfKjL4&xo&-(iB(9PK<>zlvB?mQOD$$m(O0lb`~npIp`zER zizAJZjdz$7-5)rorUl%dDy*7&t+Dvrpfw5d6tz?hAPM&}=C};=`5VfJx72LUp z@3CGVt=_-AZV!QSzj>1kkllCg+&Rnhzytmv77t_~px-6eYVyWJm}SuSC;eL)=?g0Y z*0!1SUq`R93DqFtXL8MJBTq=vI6%oec{^L_P^t-CI7|OhwzB#x zd7DvMo!~TV2F8OJ`dxZo=V}|ima?hw5#GrOGplwc#8cr$=WY-|r@}GpwTibwCiKQ!eHID@S|Qy=P6zgX)VdH*3>^mI*v!KR!8R-WjB#i(fvyfA6Eh_ryt4)5LXZJpQh zco(qsLY+}U-RmnA9?hzAbu%&FQBihp(ZS=hU^Z#Xh*iq_>2c+AgjP+y{Jr}7xvtnM zHD2SAE0kt83buaC{sxEHg$KY{QgN?U1ykphK+h&*Uh~4B*re~c!?~&uPhx`~4)h<0 zbItna2t94`!x<4*hV8r+6RC2X2YFitFKaElmSHj~wX~_JA5Am2a~OYc1S={clI8|R zDl9u+@dy|e8N_+dbt%iTS!A_U*>xGsTK3ukpq4bIIbv!3;jZ4E8e1Z&t&sLiP-K_XAl< z;@B2en}r|;J0n^BpT6QxsV&^+TepNXSb0u`Dz~EUX_8*B%JkK~+i4lJ-B`TW1bvqA zG#?|>yvxf_bND%j@=s&7=i%@D6EbC16x!OY_>4Pe!Xvv_ z1^nIa^?kQdxdYrIIcA&7YpDK7YE6~0`Y*QMt=VAz13TPu+>O2Lae{T}FOnG@5$C*GWg z1GCJ#gT|BX;c{%7{uUr!%e(Wg`CwtKu<%;10?u})DrGGf^LbRtn~ZADbM+OV6e8eTUMceLUM` zL=zSv3zI;OALNGUAvb27>nV>D7@O_4BtVjlD;q@t3#AS#Dy7bBTR_m0%3~@Fg$8=rg?ZRq}fO$PvJ7XqV4tK)7ZYx&CeyD*=K&>oOwo~~nc5D_48Sj3x zTKEy8FjXhpmOnM)(L~hR*t}c!E&B`3QnGM(96VF_H%sg9#K`ZwlO-)pkWZW1l!Usq zv8;!BgAUEEkH__q!|NtKqTvx@;&n2%-;N1=EhKw1EJ@cR`{2@M@3z3BJ;yGp20BDR z{X%<0wev4`KTS?ydt_9ZDYfpj>R983wuZX@7=}5iR5gT@(J*04btKIfcMx58cY5sDZEWJiTcRBhsCIs4O!iH_^x5F(LVYG!t(aV9heqTn>vlr5Rw9>*O)6Q3d1 z07d-RinR0AscuiXUYad_vkdcL#3I|RS2KZwmz(6@>}(UU5#p7L7oM_r1A}kz>FF|- zHv4D#;URujb)eY+5(tg-U~aKq?@t`F`{oi#+_{x{wbF_~O-n5iwO@R4MI>)&s_9}4 z+iG}kNjqAy`W6q5VToP_@srm={&gC9T5WPKGuQ z!2DIk{*Z-szLssVg};^t!zuKZ7PJ1**9X2j1Nc)rWoTzal`19tsKS(a$(xxKuGe6r zxpGtoj#m5MtRbv1W`vq@X{OF=ox48paHUm_{gmAPhnf#}nSu4=mBOA0VeH9~S<)8k zBFsVCkzv$-eixL+@R*(lVhaTe5}=EkmW_GhU#-);xviT4=#sw?V^$^|vh zWn@ER3xavBSN6F#VRsP;M@Gd>m`^skWJ94-zE!gT%FGf2 za{Rv(56xbes~;SONv2TmxeV{kU|+u7+R2E9@M(Oqr@;tW?d9jdo_7s)(+E6)7CsEd z`j^#%sms~td_vWXq|Y|v`6b&6nS7enBsZbSCpAoiKwjfI*WA2`4)ijBEorx=jyO71Zy#?y- zPi@|o4h#<+@tlpTX#n9%F+3y-8Wz6i=BXpyfBN(MsLX?UW0{zBl67EGhNxu$d<<-K zf#_%Fp~c497dC#_OytSCmEQ4X<9dyF-{!i<9ylp5e}e9^n~AaI zG0l5)C4c_Ninn(KCm)A}zpJSbtDu}tOo>rv?F5J(?T7&uGPQ@pQ3|{1f*g}5AZS?e zpwqwe%D)igldwWK*B{KTN*;x?CnQQIUhyEU#?Rup)VNsEg(mi^5fKx#PLoqQtL|h^;DPA_<0m_AS_m51P?zY(m$5A{%jsQ4*U#=KxwHk zK8WQFrC=1d#`z1^HqM@N6?B9(G`<6kDiKCWL(bK4g&Z!K)_iyXTXv;&7j>1^6CZUf zDgST?wiz5?T(o8RhZx@E(fTxKlwZ-x5E(ko-9UQEHGCdm9RxdM!To6M;5z-@#h;BA8E25ub(JR$#T`3s4M@$Cj zFNH6&=B7l~#kBi6fihR76BY=MwiFR^{L4o&Ir%3O)c&EEaz) z?T^3R_r;gAWntG#kn4d|XBfx5Ri1VCLAMLk+CGjRhLhSFPOBDuLBNiXm{VX$g2<|f zK(F?toSZ8@Hmx>qr_5+(ozcN;fv(V^o22k)f=^AfZ)SjH0QyUQrl8;F{^P;yOk#Cl zC}>G~!6WK678}02usgsb`f>HGr+YZVRAK{H&x z63YCF%~i?#LN z8SnSJZ$3UgCi)9PWKUmeSxgZ^R#If9U<^K5l@LO8lQFV8TCbOoZiije?Kx$3$&61~ z6<~{1-=6#A=4=p?5U|h|sIq%+#qz@TcUm5ksvJ|c8Pj}Grvje%8c3AE)s%9JwBx5d zGT*kHQ&ea}!$R?zGWxdfk9JJaz0?k50|veiPgc8L_w~x$ z2VO{Hwg=xV9VQe`8$hqWqZknYP;(eL6+~>9RGFUH!Vf<+OjQ$t_AlrL(%Uo z*XK2Nqu|6w@ThIS!>4TgPG{or%2t=;WJ&wk-F~Vs)YE2ave+cF3r9a_i#(XG87i;; zVi@xNSV7z!Dvesjg;95#wiprch9vNV?WA8hbBrpmM5vo>?jB%Sw zbt&1{j3E>&jIb1>uq7U`Z;OSU?K_~Ad96=>MGkR;HaCS7fZW@0$=z&4r3<_Gx2;$< zhme~^vi5a|xY9wwrwpfU$QOr9frPN52R~f#!w@j)oY^ojI`50adL;*`7D_dZ& zSgp4$z`Sz2!7pB2sK}|*+j28_c-v}hci1W(+)Y!Q2L;kL3C~FEb;gAH{F3UcMLrd^ zK#KsTp5s?m6DBtFRx^&`QL?23*rChg`4hVXqz(rWatQ+BJ8a?ikyb}_+CFw}A6b=* zuws@d=HAqNEuFTtgF9i{|Ix|Fn1&XSd1z|FGIn2y{^Mi?G%B!Ml>9M4PUP@dzILdt zWeO=3&Fy%z70~vA!W{-jt8uf(eG(Mk1usqVZ7mckjNn6^BORzR%c!$GZj*Vr-D%ebmoUf$`u^NNAqV+< zdSH^}JvX-p$zQ9OYAqz2JVBV}+Xw~}Dsosw1PwX;d z7<#7VLFf2SQTYn)mf^R$nt$8@Vp<-v#0Kq2ZhT2k(UA`n7I-bptyDOXCF(2>zPOT` zOXKxrkEZW6axO^cKUNBhO3U)d0NRRhuEx7@Z46a8x%2|mzeIw$JF>6vEM*W`8izH^ zQaW5KlNxLpY@ONgCL%j?nXHkQwdj)_`Rqs*S)d5adYj=UOw9`oq}1*t3)($G!){Tz zFg*%=2em0`D3HrA5*d0wh^oa}3kVq#&Icy0H~21z@O3Zp3)4$tb<{_zXjpGUwxef9 zk1@D5ZH*S?D0k)Qy?-3C8Js2yZ3r|T@0! zSx0qv!4(|sHm-?Fn+@l?38s!Wil-LJZMfTIJen6ad`cGF#&Vq!jC#D&AKaV2vapS+ z{2-Z~MBbr^7fK49GtF*J4v&;gSOL_6>|^$C?eY*?R*b-`oLrDK*6L$0SZHxkF3@k2 zAHDp(yu3Wa&J@MsEQ;LA2q|p#c^bev&_Hm;am9(EHl0Izq*)JgZEUN$Rq>We(3IiC z&Fefbxuk?hjJ@d@g2T)ceIYAYPM4uwH$#=7RdF{t4Ti-toX^!06SOM|>CI0l?XH7T zUzd$sf6*RpbFfiaU#bUNT;YZ0>cc52pz)JYfJv3wyL)-)cHAM~O+>&PDCid&hCU5Z za7JG&F`68v?%a`C*q0sdy$r8`y?kjM<(*Qfa$%<9R*XQQq4h9-c1fwh)Cg7HZZ?pn4YgN48!CfFTh9qjUuW+vR_?jb{()>(a8}=1P^N@_OHyeIbavR${Qi& z+1$rlr0B?Z>!v>Aieb!EdK*!q3)e|sjzh{!75GS7s1b*RJi6D;bKG6_>k!l#eKitt zTfVGja3)0z`od2Rdc@|JnvFQJ3}Mv)mE(kh(9##_!s5gK(FG(gbXP>y6pFc2&tt_N zq3ZjhKGe7g#U}IGiGFC8p4C55(-AZKA)_zq+|xMkPxnQVfGt1(#au;#7{9C-y1wY zB}!=cqB=ZWx}D{49{xiT#8SN#I-=k{f+(_DmY0Dz2^rt@GJQ90-WA?+!`Rkv&PnyU(h`m&ji+h!q`#p=&#g#2e)e8u(k-_iB3C}zEvGw z$mGl>>Hk)+A{9Wxz2P;`^@*1S`t~O1Ed@$Vf}y8+Va1r{0x7&fz&tN9e?@-F1rQ;C zHDK}${P-T%h(-Fa1;Sq)`#CWgDLci`05EEadhtt$%M*w_SX>(aebF9gAylx>`4d)GVGEhKY^JPa~Z^ zu)(PMyAJP`XJVi{yW7c`-Z|(JsmVo;1kS2qd*M-oiF(419K|5%a z{YxH@UJT>`B+;OciUlU+j~pg5L-(F+gxZ%?YbfEg<J7JL+=!zU2>&0OUohFzX^l*~51Z8nmziD8x0<4t*=)Nk}bQNPW*l?tgyHl)X;lH!tk5^=3i6~3&kDhT&Kk2RBbdTSaafrpvcVKrb-$8ydV}e?G;x;K z=#!Jow|rJ11pds@r~vXU5bb@IN#uav=vH8gA8<~Z*P*N0I1Yb2+V!{^3-@0!&jnId zY*g`*4hRnB3-4)-fe@^?GY&u}Yp_+17TM@^s$ir>zZ9chmi?fBC%OpjFD^O5Dmnmr ze0RdDZ6#wIkO7dg-yg8|itKCtk&#%EayuMFb@XInue|v@lbVM3>QQArKh;sOvVD*Q z*p82e+IA)FYjF0WDp35Dut}?A1Ya{L9HkVwDaml7==r|1m$A^Mhp6{}b>AJFX>}Ih z`dd-B{`=N7*wn+UE|!O2bc~{8*Sj@%b<=P2V{aMkmBh)r@65OTiBb-Cc z5oK;ICi(~KAJ=ENy8zk71oq;*)QCZ}c5}vM8VMouttY!%I9^;tSk|P_cC?cAHuP?f z%qok^WZV&7zZq~j$Vz)9GR$T(xYcKSO^3wcTDy7Ih5)}&fx`I;Z-6E}p_W*%&m?>; z{qy%BO!x2I|1hwFc`NzRcWLz6v|^nRo4fy43dmp5Xe6{~QGRoj^f`zdhkM4Iaq))s z5l?oQ79t#VL-I#mX44mBTef!xr?{+=k|!`#a}UBx~tsB zC#V&0-lQY(gmJdoLKmFKwnJ&p7#y-1vW6PwD>juG!Izw3^<^8y0FeKB{IU_yQEFKJ zN-Nv96B;ZEDh+;wul!BKp)tG87Z|CKo1Q@0jh*}|2;CpwD2}8(57()TQ7$uOpuLCl z3x4!UaGFL!LE4HcY{G7{CpMWWkxr|OxRSZmMQXN-1`;rrOvmEEi`uK)Bq=4#)Fp=- z=jc5IOctS68vF;_3S5MBVTccz)$eyjT}S>XaAxyh(~7b$!&n&VZ^Ic1szv<#-In_8 zC>f7-t!L3nfP}PgwxL(PcA)OwUZC5T<(FO98<#`NFsz3LpQkb@R>D{hZAPk&-*beV z@0<|iF5RgkacAJ;oap!EtHVjMB5tSdw=55ac2yM!Q5;?LZ&epRty_iiY;;n&Tt-Sf zdIXm3W*k~uyR_YT^+9h2(%XZ|$oJ;KboxJVnbqtxlYIALLDAn6lHAm1S4fHzGKSa717a_`PNU}p(UWCdo6J_G$}LG)ai*_=pHn;Im&U4xWzhZVi*EC7IO)# zG{y2v1Hm~KI1RF2SjOs$rKMO-Xq8W3{aR;NaY!8KVwCA~29lm95ssUN3JVL!odz^# zX*kL(y56vBYT=DEMn z5iM!S&1m_cDa~tsiR+}*}bRc8M@sDE&8J=X&h zPTYwF6#G`!^rWc7dStAmt9HC`6E?N)(#L@YxhHm3h1;)pEyyQiSpG#>a@WR@F!qoi zx?-=LjME0?<=AkS#a%324?lw`418`jOiH0ES-Tp1glPkY`be83dwY2ATx;ai>!4u#BCz@p z07prVYNT&9z7n+Ptc;dh|IF;s0ZzH#rpS^i>dkOESz8nZmcMSKL%L5i?g!9Mmqc>zl^sfx3L9HdZzEt4lO#rnw1o-FL0Z{<8 z-@)V?QK0q`Pa2mc8x@LB-i?4sAJg#`abpFoKU;`?{@_SD*{>q#JLI_oMrtl$ZY=@H z0MnMLd6}>6-!h+w+wLfX>}|Tzi|N-rxbRH9UD_5vx^D{xbcK-o5`77~uSLItDmb6E z+;~?Cox{ymU8eX7m_>&NIuEs`QyIDPX~;#3DxwbnIkrZbjW!)g)>Ym`MxHeJNU;Tm zzN|QexT}fwvSi&D^6HoqoNKgBSG{QTo{j?zHLFeGk8^xR234a)&2N1ykHE-Nn5>yD z=M-`Vi3G0lYC};EiRsjJw$ zwzIao%Z;XI7#GNmo9xY@2+xCd;GE5SIGu*9)PXg&pa0=S8#Q6YpRMwwgde^de1X1E z{i=@Q-tJ7PMp#QYgX>mk5{A1}(oD>w?+PBqgJ1DpXVR?9nt5teM)VXJt8qgR*@&&u z9^@nQMik2)zp14j65PJ$|_WS*K{HqX@tG1ns2umn|Id zIb`NC593|W4DpqHRjQ_yF~9P24u%*E(&8Q9v5*Gxl}18276VTeeyAax_mxhw_PHuK zZs}nUdVgYVJMz`3JVw4SmsRPlmA54g6HFx_WzglS?_7`Xj-nOft=C7&>1P4ws`7Kk zvi;d@deMysfFsIFSF08JxYw;Vh52dsf~5``5bntap)QH6u7t_Zr$J4=_0{}old#Lm zQI9u2hGmf}e_>ep>mvQ@_30D+d4{6g*r2{o;jqXWlkfMpT$4%(Uy{bljo?MO z7J(j)W0s7|o2vA;0`ZEcP~-V#)qtLqK$`nVukCpbFrDt`8?JddSaPq4jNs|=aeGw( zxkqw8IGH_Jo01R^@uLBykb5mmQAQ$5e-|b;xxBM74<)R>GM;-Hbyq^fdS%D|h zdseDX1Gef>=n8pdtX6w@IPgnGwWYYMWbo%hy%kw0;La+o)paiR>5$?p4wL{I`Zw_1W zxv@JX4xUOGQaDJD7sxorF+o&fY-q zz6&PK4fiiB9T+Ms=9NLz(lEHj_-2Yb-<>r`YOV_qUmD~?KB@}Llt%h2X!XIw&zzw) zk=&d8j8dpEIF6bU*z++`c^73w)IwK02yl=*c)sRt$dlxx9B)uqMqmHFB* z9;URRyu@@$;WEg|*|AZ5`0Gq4Wa{z-xqwE8U7iGorxUZ;05#gD*Ayh>k{bFy`j-FK zKR!)*U*uyIe@>I#^8?BuJvgTEd(Y){1H09P7oalFRXb^*9ZPJMM7|xYe*Vm7~BIT7SUYatK?~)v+_XHgt z?w?Ot&~6&>Uh1Nst7@ZW0Yc`A^OReq|BAHJx~IywT#z}7_>MeeiTq=@m}t)CM0)3t z&*VM$t*?zeH~G zsW@GDs-7ji>#Z`oJ#_#st8zi3(iJOnj9PzeN^Yfg!~n)}?S56IS~L?QC=sHIJa(wy zz=nG+_!d&Z7EgQAxmp)4z3dLstYm$yf@RBBGfw;RehLT7v&_fuUXdmyN;{ z!ra~xNc)0Gw67>t(+h${S=ss7E}z*|h77kS0%OI5753(vw|s*wkHR*Ybj`P;Pc%#_ zF*pwetD-g9)@!jxV05aYkSF_|A(9|6G9Q(T-$Twc_2t|ks~EkvP*~rn6nY1`iB{*Y zab}tqxnE;IR_is@fSolNyd5q(fpc;p>14W@nVRuwxcGqIF`vmco(W10ka-ZyI%tmV z|L_>{45g%O2&zfbCx}dT1=Dl<586fth1Zc*o39d9#v_8j3hzPvNuu8`Huu=$$jt$^ zBSAEzP#&pRB)vM0Fvgd;=eYA31qtzWM>Af#R^I=uDIjHQPCnMtL$)KzdOe;~9l(*N zhDcw^(H!2JgT{xNxNFi&g5364a2}Fm+JV&%IvJzGfJ0BKg=p@?a?Ot)BYAPQc?p%I_7c%?Y!0nU0O2alTe?jqk4H70sc1YdC_{L zB%Ixxkb*hP@f{T8bKbZWKm4V)dw0sDmUE<-g~g@R2nb;n!NO_y`_xpfwIS_}iU)?z z9p1k^d_RZCdEEOZPv}``(sLAW8ooM>+9;l*Nl+ELEx{Ze;@;RuB3#Zlu60kJ?eB7y zUYAPwc9co{?0W0A0+jdNgr|}KkP{<6*w@)CNPtr_;b1WY!h2=5!$vv0V@iU?XneR# zEsx+fTJ?(Ay*SaSUhuYt##x%)`XYT?_T1*_b;3DbS@8#7Jb^AMmrw#VE61=lRzl1c zV#YLdM$GL-EFGGizo=2+_eqmHdS%u(0?XG^$E+)58W&GGbK!-P0!?3*mOvs8jFYpy z+(2{^?tbdL=nmAh?pyFSffM^Y3*<}xKH+O{$P5^dWtO}{efIV@{#u8SeX=$WIK1G| zy`0_9%zCz(wCJLvC_NCEm<&oJ-~5(FyQtUdW3{Q?!r9$3W?*7in^}A5RQnrkaWL!k zDd)UQ&TR=td=LdKPi8&tJPMh9T(C9|^Dr-hy+#lTbu<1h4Wav&)`9lkxdP*}d#4$B7}c(*2Ri+|a#x zGwCYrW~pV@nf~4%KgK{s_YTm&7k`>EFx>X&{iR*OTlO`Y*uT*d2+xOuwjNSD zTJ4cIqkX*x*-m}cu+X5dQo|uh(1MF8$YgS^8kO>LAIu+7UBQ*u$JX zhtfos6&rnH0rmOKs}MN9t0ep!@#9P|-gLol;o&xM}{$ zYs6H8=fX}P8aXzT5}^PpXSwL?71`C~Bdk`S69&2-U7NfAFn_LFR2x!*zP8&OPN1F| z)h#96;CkV4^iFy->XzkU*!Iep5Ws5s9n2;K*SLfE(>G1;HB^JGsya)X=9Q!JOuu|5 z14~JNxsqfrFXXU)FZFQulxtoj5fk5Q_x1g~p3^QS#7;gT(ZPFsDc`&<@qKCVUr|__ zjuJ{?20qIjr$79D0Fmf1=y*eDG0fCAcjEfn15U`6TMF5=?&J24$n@#BD*tqI&0}?O zmCZmHKD>h;It(f*7lA1vkzuV?VKP0Y?^qD+o6mlo) zuKEx3&RNDn66m$APb?QrmQ!i`7^C$+`ot=ob%zTZX1U&bn04`m|7#*T1d{#fKP-U11_3Wd9`q1; zphWQSOs+Icj5{AtQ3lat)k(iPxk? zczuRFAE8oesVytqJW@BH$<~!t+@%2Oo++Vk3AJmg9IMHTU)uy8^_&$-N76!1tX(~^ z%g0JM@&1X&cr*f{K%B=hTpD(${!lWzxQZ^Yk>q}#ivwc344$(qlU$CLtq7+3G|o)B z1>S3F@xEDiD#sVFKE3Bal2%!eh3S^%nwZp*E~vm-lFk+A&0Qga{?#<=^kyrNEBjdy zbP@t5nP_Ek#c0jJUXcp7F^N9wDXUPpKcu4)Fql4C(cDcgAdYWiFM$NI$?@?6?_I>kwHRgt}%jZo%08M~rr6L_aJmxVf>&d9KTInp>YHT`U z$kN<%$d7r#cye4*12h?Ona+*+4V zbzwrYd}vq@2>JH}hl0SK6VJlzzL5wcophK|7wSD$EW*}PgV}rr_F5CDpR@PaZ$Tp} zDu)qPn-JHh1@^e+zPUl;g}|FFqoud|E@$BGDnO3X5ODQJhW;J*1~R5R+RI2hYA?oL zc*7R42U|)Fe%h1T{*s56RvX9{zW#}F$1;@oS#2wLmSTOhE&BY- zlt+=hS-l+aHSFNyz|C@jDKUuS5IZGio3yBF50Z@ST{|WHud~k-G`F54#MvHZ>v_W9%}$&zwYFl^u{LN=jm}8aq)n%C*0k0gu<{Z zG_AISZvjBMvm!ZrT>$(sJ{HLP&hz2#Y2HI6)%%8YC*HCB{h90m#iE*sY>q z#8_5Xt6fKYED%qGzVogzd8RK(?5B-fd+7cR zW%rsk(9X4KZq<~u5~>~duhf4w(rl@OBsez{HYw#pKYGPyXFiQb!4 zrt6^|{s;yt=IR~VpdU~TW>gh4t3?im$-~U5=LB}XjNU2A!>#^FWxqyN?u}MyxLfNs z@%Gs!w74w?wQrgyc@_+Hq0I94v7w<<_4yO&Lybn5U^{}#+6Ul_0v%CDyE!Zf4LI7O z`07BY5t@L@x5-zXT`ns9=G%*s-+XpNDe^_>N!#UD7l!ZyPBXaxWxBQ>^IiKCQk$egR`Vdv--jt&!_{)B-q}>;^t(N1*1#iJ|b|NFrHUP zyh8kutxbkKpE&7RN~F6(Hh=yr^Vy1Ghvav^20M8|Sh=xxK?n`~o(nxHFi^YzW`CJh zZO?wbt)_K`Mv{@|6kob~;YIFOG!{1?T_v^`_FGg;k(|1pB?|e=%a?8}5+_#@V)wg` zDbtl$=+G8-em}ieE2F%F%w+MF*&ktsobeJAT} z_l$12ek?Wf+CoWA&|)pwBSSED$%jfR8C7IPc0s^12YUZ2BWoYoGF^?T;^gM;>JsWr zz*Jh&x;)LYULS&+=lT`VVj?0puN#-_qAD{RX(ezyV!{Onj$hVe=j7g#KOhmfZXQ%#3V`CmmEOTKuFiR7R zC(-wmG8y2~Y+5I%y?aE4gZzh)6Bpm005g3d_A`Z-qzdUA(_-1rBp{W3CNGZ7$gAUut&jd%LG8 zg5u`lG95-5-PaO9=c*^iqkDf?(Sp+BNrt}u? zr}TQ#%zgftvk%Y7v+}b+|S*{ z7}q$jlb*V|eXY_PAKu_1I{G^ntBo4fuKexc|Z%nEGf{guB!3EKMvm#+cVG{N< zH6tEJA2@|$#YH^4VFZyvn8dX7yWRELn$L83KDk-Ee_|(@*8F zM6rivxI@zoW}Pe>Ff|a$K`LGGx5*@F8Ac zl%12nos4Z}M6KtkM^16^@dIey0iXF+OyLuQQE}ib7z$c0UiysXjw+^oa2|J6$?F}! zeVSt>*)M2s#&FgdU6SSJyc4$UD0G-mlB(OlxH_s;XR+vXRQWO1yM0Z(>`sb`4%heuo-`Kvwebk&=Q&_@lVmb_gXv6^ zd5(7?dW|-wuEAqKg6?4`R<>Yl@gX-U)dQ`c@Iy|dQrM2a(!}x3Sk=+ii&U53z})OG z@o6rQ06IJ%UW@WWcRyg1qZimXD~^NWbByBd| zRX=KyQYjod5h)?jU`eo0p2xxCoP3!w#8cK~k}6jqkjG6mIH(}8uQ5I3DeHKet(?=r znjXGGrm97C(?zS=K2|n{Lz&xX0YWwG0qU2r#wV49IsIeqewSAvIJQee3P5|XI_fc( z*)+V~@t43A*KaI7#;_xEBfo**^rWUqoe#)ONUiej03P~SYGjFU1}S@QqB+XnH^C(b!k~E2{xHy9X~DdUsviVI(GNv&K~aeb4+qx7kf~P#~(YVmTY?AFnUHCnG^q zl3Izzj7_g}E495CuTi|dw|Zq%Q{p?`C5q@61plaEbGL@=OCZQt(V?&_yvi~|bc44E z{X^XN6hVj=7^An{)bLJ%#v_Rpo&)65PN;}ShWGi~-S;LOp3zHtrj|AN_;sI2*5xss zsL;;$6iMqnQie}jQZxu{C&nC##UAx3oo6*sR?Gz#_#4chuN{$;u@e&)=$;lRhjF8n zG6t{Or^%3!|*B^@0tz%0iTrqik(t%NOiTe!32Jn@tvlvn!1MJxZ zRYw6Y5}ogd=y;jwQUiaf)3TlIUY2=XDA!YN4KHuJx#DiNk8Ll5y#D(;{(}kgzyEYT zW1)w7QJ!>M?*PfguzTM}Z@pOrt?%>)d$%g2IE*%)gC@g!RkUqm^>3z|IbGqUnL(|$ z{twKD|M-qi6*%CR4oCNea)8H`LQr=t(S)7-(YfA zliorktfM%u{CsdDMg(Jg)}stXKYH}2KgC#)Nv-fpNzlg@O})wbf%)k5R3^G^+6uKgkU;0qh87r{Nldx_pWk<8a%f@OtZ;rj^JzrrnP5o z{(`>Y-|HB+!~mNE9=cA|?$!aCgXuY49D{y+-u(4Gq$&abgyD!7y_3QMrcrfL&~Gh( zv%yE;?6aN0qO(&|$NYO)a0nD`v8kjU^868QXAo~oyU=-SOG&+fYx&CM50A@n$N~eo z^W+N!G#?253iaXtHF>XHoU75>UdS0R>dP*@C?6?zwIlIyo}xjj$$ZQ>ar5x6FM_{6 z*6ZwPb4|sE(2aZH@0}k!E<+1{H#Yl~NblcYUE{^MZg4fNyR%)d5Q!CXCb`z(dY+hP zM1)aAh5~-*UPSP_pEzYk)&X+Qhp+=Thq$BC+j6{X32Y;7 zW~mF@-^1m^`Q;V+_Yggf!r2!@B+SPaUH{FQ{?~JF1%rv^wyk3OZ(a}afS7A@bMrbiwdL_* z8`vV$L#~x_w)sX4tdN?Ivr{9BFSFSGWSn+)XfCNnmz2f!hPCKH{$u#f-yYtnOp52yWR!*c z$_8@cXgE)~^phvvSW*g#Xg=^Ib|p0iCHV1-?y9eO*U8N_L{yl`#g->Y&;7<&{r!{p zmoE&j--xXhRt{le?q3;SRmmLHE)uxkb~j0`L>G#4BO)foY?;H9o0ta)0(wM3LW0$C z?VjqZeLL;k!CV@KGI{Aq{WcZZKcQDQhOrfzDaX0h3(BzxvMJ|_nO7jbnw&jc90MUw zj%LTdxb;^4@gr*=BF$MYX$~t{i}te%z)Al8{r~Z1{!f1vywlE8kLDGG6wv7283Hx_ zOm)wdmry0pq);+AsDx?1!UqgCIuCOK)s^F4#JfQ!pay9MwyV;AG#44vA4|Z__JTau z*6d+7?Ro8R(0PFuw`u_c4I|{@gFqc{hC-)zsuTd`9Xkt=x-S54?$NNkrAkN`S7>n9 z*0X0b{h=)y@IOMKe`>&c{rVZ$GWN|S&PEs}7^~Qo-l=_x)K|6$2@S0PEO?Mbt1*D4 zn$m)o98*s*auHy-)0<2Z%X4@!IezgJc}zCSHjdWKcYk|t0b2o06AlG!G;^8!xcU3I zGMSR&Z(ite6-$-!M!k~!!W`a17Cz+d1#AuJTssVJzQDR@Ym$!C8-62n_?6io>?pTB z(c>8yKJB`3O(07tJHesHBTJ3DlaLF*7j?7Lap6_w>L4MjCp48i6fbd%aNmji?Q`+$ zu-eyrEYY1@{e6rUA5dRshB1EgQB<$(nhfm9ibs{m zfEAeaxK3sQ4|-N~laW3KuD^Rao$bZ^+&F7ribYmE$}+Hv;{#P9V};(ZUHKUkz?eJ9d>W0K`h!RI`X+94i0!k$>}5|KGR#dn@bz=ft+7wUU4FB5?Wqqnp3^qW)S* zG@>}*YDqA!cjpo>sfp-=bz-4k4>bRK_ns=e-qHOD8sZIGdkoE7YLN-?`GZe6=k)<^ zTpay!;2Oyqk|S5IMC`j&@1d@Pm1`_Stmlt*V9~1=FL5BdcZQON6>Sw3b_a`zd6DAA z?!?${1p0g3tLKYE{%F)(&I^@-bIeBv`(PCuK{F41V2)~|v}W>ha%9JS#=AK+x;7*q zR(~^O{%hBp`f&-NZ!FY911|n}G1#G`@{y&mv12H7@HUX{78cVQluN))RJ9?eb7(yT z`G3EI|Gj1Yf9`fy>fX_646B0g*s<6fV1Qd!3Uuq0(5jUC^Z<(@gVWr}#9LxEY!5uG z10zp&KcuG9x;zf~jnDs|FHT{&O3m;({X>Uz+UxmT!`&CHDbm*|C>D+o;-c%owcXw!1Q;@>W)9Riy7k9_|9(|+xgZ4A5hDV(|3 zwxUCUx_@qaXz6`~0#p2br(=jenN2w^{GTi(1rXV>|k*RxdS(Uq0_EK|8&CWnmI z!$1ASdiRUNF4Q#yXbi=j1pyiv-=7w*O`aYUyj!3o$cDWl zI)hMIA7nR~2n{BH*gK-0g(=<%P@3s=rDz2IU~N}j2lsh;MWxiyD&)P}x0?w0K&&|7 zg`WT&6DEsa1yS6YzZM8Cl5cm*NN0D7I5pf?nK6J?Qy(z=ord|fY4g`ad+hN)(XmYx zG}s!n+_b=KZEaIP_g)`4R<8s^u#X%T%|&I=*Ns}4NDFK6LxZ*O1F zZbg6oe4~#Z#ryLKJ>p1tj z!}j>Omm7r@9Y_bgel3wi!g95P%d1i?Y;7dQ^AAGhR}>viAg%cB8(Y)6i@vvil#n-} zIbs%n`{&B{2sC!IuP-3SkM=W@D^O8k@b30FyQki0CfKA7go*y=nT^nb2M98 zAXG{sE%|jH*MF@_ewnk zw@g8)35tHU{tbNg_A8I`Zw@Zf03vZh$&1^pta6Sk2qqwzdY$@wI>A~N^7sGs&sQma zi?b2nf$%U=5qq@&usPu0c8y2tLgg2lEf&t&yk>hAhMoc@#nFDd?$$Ot%lw|Q=kwRE zi%nJc#C|+xR?c2jkBW~C6+L^hz*zkQhSHJqYE#m z-dyA*q2kwydZ@lkvkzEj(V!=?JvMard$w{zchFMFoF?h(o@^}rMrndGdP&`Z&{f;O zAY6EGMSry3y`}{AUu{uvVN$7##%VjiW542lEF$Jn{7`K}e(_2@57;5ce|mfj4BXDs z=cU4NVQ2&bo9)ZW4Ncm1tz^;8QI8^S7lK|E${(I)Z8%)Yd1H9}sY|#?#y!89N_Yz9 zF?2@`Dq_&|Hq25!I66Y6y`!krFjcMT=EvY`((+`Zi81#z_fk7+SQL%sB};Avs+@Yi zWqRFcbyGb4)V=t~bQgO1j4=FDVD9LorsDjuuHqej?-po;1g(`$#TvbHr2_#$#=qKZ z&VlN|in}9zm%$SW+ZI2O_-COz%6*r48%|FQoE-<%Op_hfbFJ~{6Y*qe%5eO<%ft%> zcJA>XRBdjp)dW6qx~TST8f^lj!pqfvWrGJARshdTc!EoIn_OHN7{DQY8qfS$S{qYD|Ur53-rz6nNznYx5gBhaVM+ zD*Fw0YCktYqPUl~Z4Al}yV+^4I21bfEG`&NH}BEr+e`>{rbQ3gqqhxa^nlKT_jq__(c!+nzQTD4Ny(^9&?Vmdo~RpD=PHpT zQ=l_3H?utSB;WLz(b?)q6DyztX;J*zT*PUza`9sz5d+@3RpjkmY&WYzPtN?L^{5-Wr}O(>t^a-@C~e z{jX%Er>9F>S-shx|9&H^KAm}M?I~m)4SJ~=J?j-b#;-(Tq+5lp;ubua+*&{Z0c4JRf=aY@H3qlD9?{cm>gzDdO59* z8D|@;eFBnPjpk=2I;P(J zFu_tKBQWNj;VbAwG}nl#eA_FMW7?mydtzxp0{9|VYL;yC)k}siJxG2#vk6*~(Ag(| zbz$bvfF5IcCJ`kLz~l5{oh8|z;l_9c5td|305t*lgJPRiv2ZK+@lD0sI=w=RN5w+v zrzc3}15wcaA+v-38G%TcD7?X4Uu~Jm)_SC~w4g5^g3$w(-P;DhXCd6E8RIhH3!PXp z4fbfuuJiD?@lfq?06DA0O1I3{C$R;2x2X3x^DJtuk&dGVA%+bCAv0)b&fCqI*TqI3 zX^yYZ*fgR4Mml9^6dOr9PkG_nEuYF4!R25hF_wmHL8J2bOpmpx1nu8p_g8m68XW)` zWfYxAQYr81rW!?-`u5CoKYi*8!Z-)CP}2$5Jma%t4;II>l`A%Ga;BMa+nXS<^RAL! zsOoFEQqeN|_CI2v|NW|Mt1|C7Jt$I20+?AnrimVm3{qnFF+?l7TET- z3S`@(CvX7Wfsoc0+e*~d5y3}(HE(Ri<}m#A{Cl9F4&ZUU`$#~p>qc;NPVWOYz68bB zZraB4ZA#7_SrwecVu)Hx-GJPmt_7|Xw=*Vn$ykK0&(7@*T)%f4-oO{uEsyo1*^8e& z-0Gc(ZPTm}QI5*%cIIaqc!7qSJB zCF=DSF>l&-D2(4zN=R6rq>C5CL+ZuztC#Ia?O2v{t?}MB9Bfgm*B`yuU(SJW=2cK6 z*uOSD6CS075YCvL8=5GMLx2CMnD`8)$z9aENOgH2{dR9#@H(H@j-_$O)U~Zq)9!?N zW4TYokV(eW*J%N8&eQLJG1y(R&UW2Zc<%s+^CT%ndV znl@FhOnQ@8Ahjn^X8hw5aIsS@^ZuRlecKhtwTYU)gu2-uJWlp0V#|vTv-akrb?-P$ zxcf8SwJac6;9(qT<2uIIy#3O398sx8x`kn%^gR7mYXe^;*tXzvXaI7a4$otO?<))$s{mvfRzY6N0PFzBE3;IJ24{sZD_mT($@ncEKc^Y{ z_Mn~Ny|@Lgv`Pgv7DMppF$bH70eK;w0S}j5HyPgeFSX?Z3a7~l?r?QZ^BL}iW8s$Z zo-bZ)VkbWp{YYHlgbEg?u4`E=dt#sHaH^WQoLm#gB%&j+!bfroUaJjQBi7pSGFos{4~c3ssM?*qPa*~&|eTxc?(X!{yx<@(H9iD@=(vC>v5e8=6rZiNr<*flm^v0Ssr z(JFtpld0VaGL_jd5!5&&ui|u*AYL_plCcG~KcbU3Zy6Rof0vg{$MLvAv~aSrjWPDi zt2Sm4RJ*QvbkS6!lAG6)*O`dvSJ!w3nw}L-y~K+zdku^hL0gI;Hf-ah+^F+4CHI?3 z(@o=O?K|2v4m%GADD%#_Kz!O_aUc=*L(8%wO~x9_ZWVaL>Rk6CAoV)aE9O6@JiNqZ zGg`a|iTy*Ug&qx5{XAT%^_xG4Iakzxb#{42b`{G`!c!o=bRPHMZL=5EJK9GE_L*`C z^Hlan`wNlqT9L2kr$D!+@umCgoVQznTr}p5;o^@Z5kGuYq>~v#JFRNx*-K=Y<*`DP zB+XL^qD-6Jh-5o>m0VM`Em_RW3P`dqSQZeu4824})X|fQoFt^LUpwQfwi!X=YNTTPQqjWyn z6urHdk!%jQbQ?jc8IB^oZjM}MxI8z1hf9<;!|_%-LQLc7O?syb-#j3G+xt|dp)V(k z#N8Rx)4`pSm9B`l?!SN_xBb<3P7Ek{EON_qAQoHxT@%!RnF^XaOP=(f#M_S_r-0F}1G;d#LZk6F&?33TiCJc%9 z#hC0lH7pO{lr}rQzeFZmi8|CaRHdFQw~pTrdSD`QyLqx*M5S`zaBn@jEd6?2h;IGS zBXkz&yL zDt-34$2p2gv5T{3O=Rd%S${3oRgZbaGHw4Q5*XVoY8z4hQ!GNWhU4xc#>U`x#r{eO zv?8hh+eies{mBXsWK>+H-3j8dZ--B$jYLqsmro=cl?7Tw{09Z9(M;DFL<;s)+~v50 zRyX@KMvKH$E|D|cF4|n0_6PS~X=$0y$2Aqw?bM->1I*-GG^mZx>&C0R6M}X7K7VmR z`sj`Gv#r1|wUIrrI_^E5d#YGAbQJ38No}iOULtC^l%Ckxzqgu5NpwZFHRg@IR_)#q zr$d(%(G`FtlVA#_vzjIhspGc{xWTHy9g*N?!kxQfLsgluKQkwLKIMzvG-$~o&RMns zJ<4TvrWqlW;!=53$O7G6`qXJ(`3X8e#=*omtX0az@w)$@6dl(qDG_gcstbiL#6TrYqdmR7PpTNDssv)A{(fBcpflXZt?5QxphkB_=R*X;0SY4OevecUI0{zr#{`w2SK zXHaO{2=h8I+zC9grPxvI_Xy4CRHfC?*;K*bkUx{XV8Hl?Q1Wog&5eo_HFEKKi_=ov{{71e84qAPt3n(rFRRu#l?v4%nQsqQ zomxbh>u%4Cn)+)@3-3vaH<|1myc`h!y5&P@HH;B`uwp|G0gO^Xm0lg~G#6~(ihkk< z8B2XMqimrdBWnX*yyw1gnmy*A(jmY_A_9fDk!}8R?_>MGso@l$Qe8e0b_Gxl=Y>S4 z#4fmCl*i;SU{g8__|UElHbVa9xVz@AJN=8}&LEq4uJ#mRPjf7?sX8!h`OPZ}?G9|M z@J`&fy2eL1t-l#gzMXX#4%Q}PQQ26@2HgzBo}MY=ZUb05Tdc*f;=P@$i!ScVwz>}3 zd9*Z9g}TBsHPBdv>Aav}tred?lD!MSCdP8E_@W%7`SEqDpF6qcA{4|T6tk18Yk)<<}_e zcYIgPypxlRtm3u&fP4i7{%*Hu;R7=v3?bD-b(-#WBJ7B6n=XV(V0gJeA@uF?lFshS z6otUI*vJvi#0(wXig7z1Y^wL2XP1PI9qxc~1$pg$NY5h$fuc~wXpF;`5&7;YSOUl1 ziaI;Er@2p6Wl4l8BU7af?RF}H%C7x_@hXA(RHx3wd{tOWLS4DVfcydRMQxh?rJGGp z|0c&r{R%mLO@@SiF)}Ldj=8H`YehIEvNiVHW?Sm@^w;WbFHiKgA=D8@DKIwgHH<>V5Iw+h$3wIMb_=GYaxiyR%rZY7y+ z)g`5rHzt7wuX9o@Oyv`9+r)MXK0+&I-!0YVrwhBB7^)}20ba&Q974znVkM2oPc+Cd`@sbuP0Mi4`RDxZmS@Ldu5L-QLg zBoRY7s(~tN#O0O+H8OF$b$tAzlnP+6;wowoVn0b_cbxSvVj%UA4bcT35CnO*@|EQc zt@lOvE&&7ImjmCWdnJ_`-DiU<5?^ZA9OiVlX%sm(`z{!xSqh$MC;Fui$7Uy?$boYN zuF+5qy#?LGo5IfzbhH2~>GTPUkL9gbEZbw?r(O}_D{HWk$jQJLBPxK$(LozuL|dbB zgV&*17#k#@I@oo-7mWF_+i>d61PCPK!Q|s=F`jCL>ORia^eZ>D#wka(n_MS09WUj? zC8`#x7Z{5}wKF`>pkHS}yY9w)L@rqx4?op#GvFX#x8()?nvKs3xR+_!3La$Lod^9Ycm5Sow7d3#F+fRC3x^d@tk4`%CqVPY{4^ z%(`~eF?;Q+R?U?|Dpmw!fk4-AKg4roQ;LSDCfBd=aFj2j(%iBA<)FUn6f9H+vwq)HfnbTYh}AU zt%;n4f%-j$%32+X9g{>KlKdIBJq+YsA@BB(`L&VMLJ!%0mZa{MKAy|~HU@RjOYJ=M z1y#Z#Z!*_UPsvT_u^+2Q;uz}{VuhQuVGP=I(IKI8H}dUFzw?8B0m;m#4mJC8Hv|?w zB)luOG2Kbv+3Ieyt4iWGM^-!^bM$;a$4Q_PmO6I>00gIJgL)pSQ%E$S`8>Nm(W|2A z-d)L$*DL9!8#zy0$89|}MqzhZMU&X(&F)I5tZ~h?`c9`<=F*Bd3l~&6*|uf6P3GS$ zfcky3(E43UQG-vUK|6AnDL*r~HEaGEQ zUo4;7eg|>$?t@)6k%dH7*#N``v;~&Va>Ua7BL6)OUzEOIz z9iTy+`Q|TII2Wc3W=qCRZj`JbU*(!gYMJ zU2y0{+3USC0?`-|duOz+2Wu5xJZ89VP)Q_@n#xhD$}W;{QglU#&t7x3E!duf@ut=A zj}pIRsSp~5_1fclWUojvpVjBKJ=D7Vh}S|!kb@Ycp3>`D*~$=R>4QZl)!@p=X_m0q zw%5?Ss}jWi?H8=XSF#^v8W_;Bw%{v>9m{cCuidUE^}bAGCpuhKLmGo!^TltdiM^VL z&sN4+fiJxPi(g-zQNp9$9_L6_E!Er30(NAfF*TzyQ4gnA{B>M?tcNYR+#+_glueP` z-?Q{xAeQ~9U$vmCktk{S8#_C@LD2XF0M&{B{4<*-8QdZ_G@f{0t-3ku@!_^YgD=QM zSLRn8$OyocI_0E0_a{<>zOMIuOHBSyso&yC`f*~j(4EQ>zUtE>ux4GY00k8uliHsF z!dN9f#xLuM^hZJL8l`G&^adF^ssa>5l)j1K6c}Tca zap0bH_(^quI@U^A@3?ltb|KI1O(b*ipc3owrDd_>pCu)iGzGN9a2t*$3sT5;YX{zB ztB?#gX6Q)SS-Q}bTOy@P?FxD8PIL@TcDS~XkU*oIOk0~N=czCC^5@hJoyAo5c=MY~7WS7HD)b3k+Kgaz9%$L8^263mF31JFZfYhXe0U z`_nu-_a;vtC}x;=mSc~X#lBQHDZlc^?uG2_`JJ2yP54&5h-5a;re?SIAXsX45lKEe zg0}H0dpGdxxW2~yE~qe>1DqAA8YFxs!^E9L_#pN3_)r-0lADuU@@{xd0qEItV!n;3 zss-MW3w6V>XJ%(wkfe97(yi2?!~zdzTy+l@olho7L-{75WeJmFE(1>n4mE@(t*PuG zqs3V?mL3geg)V(5lZlM1i1>Os&y*6)uNo}EdmhY(CRZnH>JCQuF~ihSD_EV1jE16} ziYO0~qmngx%0~u4X73>)>tzA<;rl&H>-YXyn*CDgLa9ZG3Z#5B3c2ZYc*rT0e z^PE?9NCRw}wYP{yd{RLxMB}7X3x)f^?wcA*yk?}0^;8JvoeC!nu=Rv!l!mZ zZ&f44hZ{e^8`c&e_c-W1{E$npUg9 z(L-igxWC`Y-Vw+S-^tU7CdSvUqa|lMYW6(|vG;z1TA~sj>=lpYG_H+c@B^g*>T7pB zjL}5-&o~g{-511oQmJyuAvxyD_}&XpHL~#!w2w%+FN&GZeEy(LH8!xJlY$)4^|WN} z*bP7giJ(7Fnt0=L4ecWgp--%u8tb%ec1n7GJ}Eh%E3=spy`wZbcHt1CqtniYRWtf)x zZ{B)mn8K%ujk_6aqRu+!!GECm~ue6TTgzOdePJd}~caLJve)YP}SLDc}^9(LN(*FK*4nB=da)3Bi$CTo@$|V6Y z>HFdwcvzAnV~K~P6QSGIuHZS!;S*Yt;~cv$>o#0f^vs`UV|?@itjxiq^jitB>D^FI z?svjwQ(W=|AoSF(ws%IDo_-``B3Vp6j&BiVZ9c5ak8COPuB=!(97&aa!DxiZ8_#jC zU3l^h@J*;~D&g8n5ctxT8K0BzTN;<~P9K7I1i?+pqE`_GTqfl6E4zWB6Sx#V*xBaYU)=5(ld zH9?WBPlOT4tJ>cQsVtvsH*90M#B7cp?4j8UYEH*!)7%S_O1kF5_rN5sPhIR6i~IA`qB(ceR>VwX-Wk;>zr^td_YN2j0#2>GQrKQ9~9k zD12`u&mwJ}#Iu|5E!d(Ei7Cvd$$``=gqwNg2JwbjnzH8V%Mm`i z!K+y+c@{}I!&k%e>dl0ZGR}f0z(n-LQue(eFGs=7ynF1DkT(&<8y3CsS3>t{~1guW`WnjlfE@Ac6wyUESfG5 z2tih}-cibov@UDTE7t>>J1yYxVTZ<(@S>_0+UkgtZ#B;ownl8SoYCAhLbc8v=MD^5q6j$SXQ4ZnUTg^I``w#7P<<&aifgrF7Xt9WyOrLIbPKHMV zqi{hRs%E4K&dYLhJZZ?l)oA?3+ZK2tXWNCw{`LU|>TOfK3S4+~=sa`%A$;C^=YN{G zNY?7JCDL3LcdkCb2bb|%$?oNie5?=M(3nr{AMkfUCX%r``j{0XYus68InssC1uOs3 zAit;LL4qGoaCwEb%z~Ee)u?8U=9r%8o7meDhXTs%tGq2bE!mxxV#LdqdY(?D6(~1V z0EfaL2PxCzvOMy)25P?w)+CIy%MXkcT3eiZ2?;1<%{3@dd7!KcAyQcza&F=%t7y2w z$cacm2H$Nu-U!J^OY1)B6^(w@!0wp+b=kQFrBtrKC-%1Ny|yK`I&(w{GO&Jb-6=KE zsfsW-+4@O6(6J;?HLy#ZH6S#cj2@2zYkp^#MKX{Gt@JJQ$X$|(SE#8yyylwD%E zp!>$=JI}M6$Cjoj~h*2ChLU>iTAznSN@gwd!r1j2h9RFR#AUBu+P1 zIs?S~Qq8V_>F9*5Zdu#`&_K*Bo(|e&lh<&a(7tut;dkDKIVBWP=(?bs#~v4kI%&y4 zsvp$^l$}ITS$fx?J{9e17tgVqZ#PQ74fZN36-s9swC}IP8IFjd)wjO~@Sz+ft#4^a z<<_Xk4O838e14!$VXISjem8^`8Bkv^W&i>wQE;L|GS#C;dXF=pVguq;o9P$^(FGLq zV8x_u;JE%tsk6j5>S_(wz|ps_7R03(VohG(yzPs;nz#S4@AFG0kuaZ&ul@3mVUeJm z{3`i2g(UQi3VX&jj!(da(P`2n!fium_jF|V*$g0>I$X0-c`t&f9+r!|ly=;N_BS!V z^+RlRSTUe;l*duw%pk$Bi=bgX1Q;YZCBN+<#$T}7V2pC`$6Mn5l-VR?jgrbiVu@)G zQX$z6skAUGPf`<_NiiMheFU7W&7N80$tJ4C{}oAyG>)(iTh!FQ2mY_wl@a&l9q4%I zc*VxJiCN6eq%}DZE5w4leCOHRQ>(0bFH&8mbZz;eA>Oa@qqKW|K{@B`G6TYgwNfr| zT_i;B)NGdOQ=AzN6cqg&O4(jz^txC}4mvwZiuVQ=>Os}YREhg0Y-_}R=IIyO&PtAH ze>kvc(JGe!5?FFpx}Z#yklXh!A7qjo>#FK3ZYt!^BbVc=u*7g`;nd{8+*n$h%YL7& z7j+~I0ieQ>0_(TtBYs?)PftXCq>!{cPqMTeP!^D`GBB=f-E)%^?kXabU|K<$n`D)- z04@XQvaO7YCAkYOT;hcQ^miQ^5pa6{@_L#0p+8`Ix|ix!*woEo3hIZ%E9RA1g~FOl zT=Of1BHG`fgtr0+5%v7DkssopZmB8N>T4oZdp+}Tk~9ZorlPf6!W7?^gTTJZ!C_%F z7)Y<77L}mp1FZc{q4o6j$LPZ!UrP44P{*C9HHU*(ecU_E`PFN|Xmb)O$zgO}l-3Wu zlD+D!<=myONPFI>Zn1biNYqfyh#(&d_lZb(y|k zEQXEUvq&^U;`7&eq)cas?5QYT4gQE+EBedJ>GryzD-PD>0TvG}cSXICyCVUa8kEYK z99mhCyZ7Z?c+pd@YQ6t*+_@jDS$&?5*nipM0?Wy$;RdL$%Sl+H95L2>o!V&I`F-oY zC(%W@QaOf>nBmqXs!+X>3YV4D3LblFgVAWcWV6UHkd7MBaf;-&j?*sUtV}AjC6*YK z8t{kpT59M3VN?ROeiQoDH-DL-Z2zY#QN+g1raf*B8iQ{lRw=aDqV!7mI*doY6@a=F zuW}o-q2Bcnvmw8YJN>cUm7M?QT3+Q6@vY@*Yc(FRCiDreOCbL9mN*r05*5-B5A1S$ zz>M5c(^ld9tN`o7o8zmUY1^7 zG!ZB3oqF4V!8q55OR}+Mkl%v3R9WSCId-(LPup%b80!u#w?^LII428CSn+$W6GCYG+8% z+1Z@iuo=06QM_GhP|FK>iKw=iH$8U4L(a5%@;8v#yNCmF?Zn$+<8AVVHb0=yV;`B6 zK^+hEc4xlo?G%DE(2pxGbP~CcPUC@GMb2<8Eg`Sen@8vWNtYQ=2?)!`3Uj7|(^qAQ z(6#-o{v3#mbi`zLo;9*u*`{tD+Z311u2&E?ikvd z7i@y*r4ZOaeb=Ycrw92-J9+77)^){LqYvPwW7$_?m${)!ZiAx6pFPV4?G% zUV>lv6;5Th?kH9UjoYfYMyM#zYEOLGxgt7Q|1gOlnvjTh(q7Ts{KrPV-bm)G4*_w6 z095p&psr6m!Dn`M>CVoSrb@kT7KBy|l;I&l_+ZrZsSm1&+~laMhvM$NR?j+^WmRpn zWwo26q~EyxY+{pg=L>d)&b?V06Hg8)zbmwqMQlRilZOFhS3VEUw9bdNdP@4%JE|56 zyk#vp%mCgF){c-KK1Ia*r9-M({ih=%B_pJEGanh}EQe;>!ruH9kx78XqF~AiD{j#U2D8L0wO2pa|cFQ=QwaYn~idQCxD6!Yn=bXuq+H5Si*o zp7sKU8r(duP|?tfx|JS5rladm`XpLbsJxRX>5Fz)7<7i!DObY-mQF#3>R;WM<;?CK;s$`L#ksc5lXF z$KvIu1T+dqT}h*z?hW4(11C;UK4XIo$mo>;?<&td-a1EmkGBrcZ_ioPtvp)XH(OQV zz*ZWDviOe5?m}S+{LgN$$A`qW_ZJ)^{OcJ>$~*Ig*Yj4af>Wv}1Q9gzCcbGePQ(2H zEw|UfRZEKIRZCX0R`Va~Fv53GFh^mvl}{$nvI@;FReTQK#i(H=HEz91T(*si{S zkhg~W@;@ni8%6JOxG!(_FQ44EsoW3Z>WzvvsbiY&iX;)~g2nSmA6v3TLmJ>Go}(Wx z@nG5rdHw70Y9KRA1R0$oW!?L{m2ML0myh@-_*elPe6)yX!LycLvn;i=reD{cyFhQd zAgAAGPWaTtoqFHtuE6Mp1KDuV3Q6E}li1vgL7KjE+a1MmdN$!IO#Vs)o=9mtH6N`L zDIJl1fVLRYAeJVkS>fyr8K3XqY@y52~S2OdYddh#SHq5Ah^6D&r$FDSKjCOvr5+sFKECp$FAJ zyFh_+z;bU?Gi^YA;LUt$r{Rj(pei*Q^dl1TT27*2be|$wR7YDAM$Cp%-4k&b^v`Rr zrs$VL;Q~n2^BOEdp5O{y&+}H~x^j^-;B4Mc{i*hJW2Gx+x$AguvH~dqyToBP7xO_T zvFL)f`&*Ma9@2c8!||t+b7)w6$Iyt@bbLC)i>RY})^u+>__PbyL&f?=6IB55Vjx{1 zh-o8>fXb08Kz<~iIX}m#jF3n5$E%FGoqpL^;UzqwQ(k>Z|TR-LP8Qcxh_tX>V#;%P-K&NcFa*co&-Qr9_-ASu=W%**Esp z+4`vVM5|}K7|9=v^zcEB);gC!Tcj*3Y#HB)OpF(DV<)MjLIlueRkLu-M2?C*rYpmS zPT=-18>Lz}ipgFu8ZZf-ON}^T5wwaw@f(=58w10KS+9YI3a{-z>~tlvRM)9Y`G58v zU@?i(fTI}Iif*Ggthrxd`4vCc)qZ{U;R0AG4Ma|$sBlP|Ila$Pkjo-Pr#w){WR5If zwQFA;Rk#3NsU~UN_S^m9hIxN_kMLfKho1`?4u;|3Uwe0=sqVil2nLmn=6`H;TrtEc zsO6T3db?}w3 zk+=Ot@jDDtVy$Aeu4#50epK?&vud{z;eo`sDi&?deny11QLLJ5c^c(VA$ntPDqQ^CPN$16b4EpejM1kLe9H0T z6W!;pxlwEL6gL*6WuYy%*8HH9AEXNRaTRTMDHU2rsUf-SR9b;$tQtEt!h@oWH#57< z%`AIj^!Ej$MoYv3F1lcxln7FGeAv|3?RRaMR6a?bHr#XLl2lO{xHOv!oW zEc^U=v5*)#8e4=Pf0o*NrOsQy5v%RF!2`5H`%dGwfoAVm5Y5elyc?PBJ{aKkUO?k) zB)324`}7b?y^_CHyA2QX7c$n3-x0y83@US#mB{s8E5o8HzijUsWvW-4##QCW7U-fJ zm(nOYeXKQ|8RrF#{#6Iv!D0i;>5eaBOh=L4%3FK>;9PWZq20Ov0flah(<37KvH%Q- z@)(CGvQi3|6v*fFG6cb2Yc1Ror2_lAX#R7Akh_ZcRV$_p)~Q1KRy zQfIfTUANHPiH$Cn5~CSkCU}vgt8U%&1UOMnk4kjbtOv){Gk6>%g-^{Ebw_gYjJwnA z(CVg@-Gh0qS*5&W;Vok&-`YEY40X1L%FH8zP8svu6yZU!1_z5-_{Vyp$8mDN&*0n! zhZ^BA*r*gp^)cGZI(t;_hi=LWj0zLZ14)KG+VzxD%%*sel%do)M~BpEm-*2@(##Iy zz|Cu&53jWJQ*60mh=@Gu?Vbccn1^%KJHjlooE)_G=TD1Ilk5HB+7*)Sk7%h*#wLI8 zyvWP=gohr})AKyKM*c-Om&F17d{XQ^U~WOlY5Hon(?C@{{cSk62&3{9p&zO7xWRhR zb9c{w(_a4BGDnE;y-d%mfpUeE*DT8?j&)Zp3^}%Mb6vg6Z886yzW%gRRUEt3jd`9k zJSrZeHlht`!8#W-%gr!3T0%DC&lzKAFrsD;mh*+ygBMZR%21%p6}&d!;O#fTU5L(; zKZYaMg_O1Gh@R$*A=j70x0W(Sy&fUOK)4FQ%G7|`g|4M{Gx3VqVvfqK!I!DLg? zxGPtzo4&Z;@qw+^32FL~(63I^J|)sW6vUJC)^|*#b;NzH_sEHR<$$uBerai`Kh?JT z!%H^+YF(3rJw_1U2^Bf{kf7gO(>GS!&_gVlXJ^CF?{QGaVcTC)bA05!m$+yA{lg0L z7S)AygXY%0B&$M;Aq(^2(lS>yF5N`5Qm-cl=48CHSOnh@TCbL~sad^W5lVZL&nCRb z1xsu?czA)@eL!mvydj5zAj>Yb&%zJ#1BkIepDSuMz z=%zn*p)a5rm3|&lXJd(^1udu$_0IXt$Y%CO$Ah1IXM*N5%Jh8sqiHxORQ5 z6#ST5h!u_vl7EcXTW}!tqA#fUDqEblis{f{QbXYtlcya)>)|;V9it?*lMLuiYXDh< zEt+Zk<7FO<-$h=bao06^;@hd#I;^Bk<)o8W^9aAz2DSPW3XPyDnv#xlM*w0%n-Z|%ZhU9xJ?j<|Nn6I)^Sm; z?cVn?KoP+}KuHmSC8?w|ii(mdFm!kK42^(fcW1t4IIpQe7H;zJD~mH7bXNlwx*Mggf;oM!@nFOrdw zqn=?95Iy{Eb@X|?tfa)TH@lB2esC&1cW7}nwZRY6PjZ-Chdp%qpV_0GRH`C)FYizJ zUa9iF2lKxLdh{X971uzXU^QuLAPQ8z@+=GZR+cS8i4z8lmc2h8Z{GKf>NxPc@&wPT z9fz&wnawNuYs(LsX*gEt%^CM&k#Y@&K#9N(H1uWLYyDaPBC0Cft|qn4~xr4cX`mHKMW#v`3ZJJhwuIROfB50$Me_gpJxqp2sJ4nQOW_64`fPU0*u2BA*j zKqVj)!6FyO%2m6ajeO5@i1@1&U?{d!8&Nnq&DM9pZFdRjA!pTyhHYrAzS z4f2Pw5|tGItG!2~*xcBqr zgec)yYJzRT3s$bd<=C2MucreW5_-%*7HOKSR-2PV%~QXadNN@_N9F+J^@7utA^cWC z_e!-*Yilf#QFUMz2K0CpZ!e-p+_R-8byX*N?ekCc@P4gyjDXkUv_B@kL`q< zkJbKahkSDAkJN^jjFoVnPA$g)~Oz3d(dlzPf)#XsH=7@1>zL3p(uKm}{1R&Z$_29y9vLDjx>P4CHZ#We0& zmte90-gFj$ikmQ zWZ5Pfv?AM3YLAdP^)5%Ktj+`KHVq=wup&AQOh&%W=vb>FeJK3x=k6C0J_H!gEvJeD zT)e&+5X#)URmDA%pDu&uP>6ldFH(3!1rvF7?mV1~2_CwWft_H@RB4a&pFhaSo;4ts z9|j>xVV*++j(u^14rpjqJ_DFX+{35Zo-WZNh`pW$hR%^@`<||!Q;OlMIHo?fCV2UX$!{j!2NTWM=B&CGDOLsFT zJReRb`2cT)KAK0%iq4bpBMx|P6RpL%e;KGd(q3Y1BwcEK2S0_ zryn54TSNp>aE#D|IxwmisKFlNU%?3}8^%D-*nRM2^M_kDKD!_5bv4j^Riq>)1mi}` zSvlmdQJvI?P5EKHTz|E6d!s#zQG1=+A~!AR1nobV@~!Ar;VN@x+{vm33Vv7=V`kLKRHK7SL;a(69LR^Z9n!r^2Q7xm8D6f!@S?C? zAPnXhle^{`LaX;UGiMmFg9@npz)$jgdB2+DWBuG|QQ?BR_zXJUX2cD{Z|;cHbpGyA zvjrDYG*ZhR2FqPcfxs$WKc^dV>+Ahjk^Uuy= zw?P2*3bdvBgjc< zI__DvB1bEPS#6b?9We-P<f`z?|A4e;7ZioXot+vSHvf}V8sbeoi+t( z!<2|0)?P`C*DXmgf215Nqg9SbkS^^Mcq&BF=&Mx!mi--ScjZo}iiRus)}A>C+~{zQ z&DS0|du!`;i2Sg}urb?blh6&WpFP=c7uZ~^#;+50|`@XvLYsWEx23su5 z{;NL`Kg$PxFE&h#S#G>4hjN^@rdm+?-Pwu)h<4)Us8GehMEq?n5#P>bq>*$tzCU&C z0y#Xj?}oEKzIUwhI!QU`f0y6j(yp#s9w?A*AnbJ4Bf+-u%BH1!o=~p_%|0Rp`b8@^ zp^*^0d7QE&m|j3l+hb?v6D6|#7WOPkj_+$hb{R&j*Pjf)N5T}hjIG?g6HE`T$0xSv zZfV%mu%$|cl2~)kZGf&)byqMLrL9=iRlVP?k^TT_l-gM}0q`Mh6UkFn^=&H{mtKEg zpvJ`je27okk>E&tZ0tQ+x}}w>iaT7*rjNQ3MGx9eC_H0_f2_Ha^afLl_bH`ttjhO$ zgOqf~O@i%siGR~v{R@O`$iEakpiVoTxf)2AufN`Si`8iXeayH^uPd=zjBdJ*$l$;vEMYvQK7VsQTIC11yjC>dEu*GC}fn8`LUu1)FWE?1<|Q z&&j@p%^rql9D7F$UTT8+I|a|>322Vu zb8^2(=SD499RRFWx)fe)&CSXy$!tX1Cb2IA>ypsUS@^y@-}OT`NWHjFy!{Er7q;D0 zavpf?FS_@#PL;_eJsACR@d!nH_)X1nR~n!LBu>e^tGb|Xs`vf|0<6>@)h<- z%AXRWhfO@_OJ8nyou?@{iFk4z?sTsAtW{00+cO{F*1F>(IKL%?Hl38>3ySD>*-rs)n9$4OSeVY_cPT^j!We59o>>R>;mYL0MtT@Sh>h?FbHuA z){2>@#7(+bTo8|DvQo|4Bx&ikl^aTlK_HD7?~yKdm3*N+Tn<*5llW)liXj&a6o}p# zzb0+~F@6C+WMt~QK7Lb!ydt_Cm~W4S(XSTZ9rQApHkHNOf!z8vBu{lwiC}nXAc{15 zK}jd?)%3cEK<==0ZeWggKL;H7a9G5n@)78%%~Y;{W|d*^m&e)&)dwaTdi9H@Y&}<( zz@Ta;e@t9~DQ=U>GNg(fH{R|74iads6Y%x9!h z;hi;sQ@cg&w!jBZu@miMWF(u8{P9*&>TKP);{5SSgn!L zb=XwIT0F8JkJR|d^<(2tXH5C6##Y>I%u3y{F6r|LxL5s4gIAn2p=+6^f zNblHFZrn93sD12ru7Q76w!b^1hGneTv-a6)$K{`zpCn%-?l^J|;l1yd5*{9I*f#)L zOn#Kup;U&EYK;nqN!7W$%cF%xsD&KD@KCX|G#ZuM$btI|^HyfDMXrjK?3&fe5X4J&9d-^d^gq zRi6i>DMMjzA&{~Z9ul2kT7f${XQE9b)noH4rZ%?9u*Ch$Pn1YYw^ni>{RcnZ=dYO7 zXahJm(`oy?sssC;LgoM$T0X7rU8s`JGO_XXvZ=&V-=GR*ra`G59T#PEPf#S4qr2xO z@#gUX{j(MS8hS8T3_o=|u_k{s{vte;Bo0h4t0?Q;uaEW zv6sBnl$S#jJ#ev`rZMs6kx(P5eHVPf6~nQT7#2X94XL4=SiNFxl_CS1xaYIJtut7;oPQrf>H`EQm|ncF6gr z`Ve=KkVJPT)k-vdh`p*#pwci#mF7`@SpG@$fU!z?evt5^)7_h|>9|Xhwr=(I*i4F@ zo0zR8$CSopKn6=X{Zoiv2an}R>gc$e1$}v)w3HCpqvv7M9O@-Dpdf&#=gWPgf!~yo z3**>_{g9d~7PnqW3*6EiPDy$pdS$J88KOh*GQk8+NL?CKf*b^D^AeyshlMMmd7VW= zpyzyT9`SsxAHZYa9Ft!7RD@Pl7f~ExcU5$5Tr0WCTXF8YyNS#DRs;i6W%kr3)1gdV zrTgiT3xkkyQEFT`OXb30bvLF{Y6}`f=2cn+FzCt+P z1FAYYCDJ_M(kEtQmY5?J+eoy+$jlQ0_@u|Y$E^o<=3mX8LdmwC@4lsNp;Nc3&AcEO zN{EDeeI8G>A2Z7w=`F7|S5?YU;}i%DnT(A#&VChV9v#IhIQXYbbO+I~)c7a=p^`h3 z*-<%Qc98wc-~rglmLPlY%O=+1^UgGaYI$o=t;+MtFG5*SgjW!@Z^*t51bqPJ_*D+D z_?LNci#pF-{b|gzh0SMs1_nmXWp9WGd?tXm!_7ZYDQOy>#Z~49(7b+XJ|uF7_kOj; zna-x|2Y7X5#dDea$F!htMPrz)GN-Jih*xA&FmF*k;fG&BT!^*JBK#~9!-=Z8lGtICoXgdxhB`=;oEyweQD1djt@KK>9OcgVx}+x$vJKBn_pHx$Rtc1pQ_thwijquUCUoGQ@*WpN=9jX zI)9{f``W*eFw0};bktlNTRJF7t?kxu-}e}k;dWZOjJFknTCxE-U7m1P-7(u$tb?zv zL$7#@C-6s5Gqd&&lw#BVTpka@2yUp7FaQ6U)&KXiO8I*h)G3H=x#u-?Bl8LILvpkt(eEp0;xXJX?w4|QkCeZImLe&>D`3@zb1VTwF5C` zi2@gTH%Y;GkJsQ&y}Zr3EJ@`&G)Y3b zsD&}+w+smFDF3vtpR*(8F>0JE7cO{we)C3_x_sJ(8dzsm&1$L=qp zJG3K}-$)u=JAaAgWhNwiAn&76c~k-KuYTYgi7bha#Qe*zUX}`OqYU-W5@Zd_?>mf`F>%AS{7BrMD_ey`t6QfU0+HTCy{5T|nbk!3bR z^D;# zHhvbS*l)#VI6iTS7iMN=8Ke&K{V$72_$?aeK76=5k3AA^Z=r0DNQVaB{i4X`<|`=k z;O&2Y=s&%B2bFazPt`W}!r290aPj^>8qbE2r@3YemHbiQ?QLfYn*K z`i)}*0o=7a_vc9jObiVJk5PvfE@~y5t)r%3LE9hJzX0p|xirr6VgKIGZFl~c#UE5w zmnZviP~)kmdK6OU*s{}d&`ahA4LCOufoNYx@4jqW(v}CYutQJ|8_E{lj4- zXEjv{4pd)x6NFKydLwGLFhp#%?)PAKi8wl!z zHtKyhrZz6i9~;IMw{Haf&N6$dQb(lRysKampiT$rbXW~$Qt zgFWg0dI|2s8-Xm#08~Cy72hSSIf4qlH)1Jpouh6g!TMnH_r9n`9kpf%KTSSk2atnV zN8T=u`Yj;LkyIVEbqM|y<)0%fXrjkm+9dwJff^VA0r)LroHRKA!f4%U_|N72U$OMh zwpxitz^In;NwCpBy1o9}4SiMOb{+^Mr`uZFe&@~q6`KFf_r9tE!brPWL!;k$^UJH? z%>=IuZv8)=d+k`qcYk{U{Es94@4p7-fK;N~zKHw(@j?In)&FA?{4)@|*(a;;^6zY3 z-%e3Hnu2OSby51?uK$fE;LVQ)R0;pDCG!ui;}2Glh~&j1ekpJL`$Sy2BT-Os+noJ3 zAPs+k<-BtP{qjl2AI&dJ`C2;=dr-0TtQJUM}lDJFfQ|VLB+cy^&CT zGb-P&B8&+5LCd+4G7B;iz=sLOWeV#ZkV6B0^U+cLhJ<7MK6r?(rLU@th<5RfST?j_ zBpi&5GZvEMi}Nv_erSehslV^*zt7V@SWw=gY`P6?{2ixJ)_H)3Asu(A%q;WL!wH!K zmk~%AgE6%^Y0Z-Rfy$=@J+8I%76qgJQC49t!+Bcz zClE`yDEi^(FmZvq?c(E4xQlTjx845n1LHpz-^R7)^4Gtps?P+TitEB@!~pB*QUtjIiHTQFApEjmn4&}sI=Q`;T6Gk!{kG2$zGZf>5fQ8DTd-*LVK=rFt^ zmf(MVSqw4PF1x43>}hemGlNg;{mbHWzeR^xVFAbrI%Ll&9p-!Mc)IJJY^J?~v~&=e z?T|$A5!=NVAn5^lN{aE3+etUKeCga6UwE2)Zkmtf``?NoPP<0hk$+_X+5E$z`Z;8l zoZ#A$ozh|K{tq37+FV{iA*OJW_O=D`b#BGQB&n6(p~LuHFHnYGVP))_Ned2-i(B%C zTY>Z{`ON$wzfPkuP|dt7&K)^DxaHa@iB{fRF1?)M>8K$DB1G&rIUU!zTo5a8!Kbv3V~}U|Dtif zP$Eh@h>~k9XC%G{ObuBK={lVFktfjpGr{Kn@;Ew3e?!6#d$>%`0z9_h@5$Ms%Y!;W zPxAT^<4Cnmq7BWjSG4{(n{pORES@4}YTqhZGLO*gi`~EWUyJ-7@2TEL>Zb)Fah-4` z@!Ym>kV-<0)`qc+7wApABMm=-H6J4W@CDccg0{xgegl{CGWj_neAtFEcF!XNF+w9k z({wGcR$|cpjzIh;c5>-PFErxUzx8d6(@$;YxI556RP9EoHB()&Hn8 zMqRxO36iir>txP!T5qdx(xdz3s8Zt0X_K!mx1kn1g$^+_4B10 z6}k?fGE~IIv$S#locCLzw7c&`?BDGL{Q5y&g`FN`nJ^8E2ofl9`K;Cw2*kYKasKLB zl&B%FlrL{kI{~vojQ}nH*fp=?YiDLwE}MX#YKGyb1qP#-wzf99jt<_zXxOU#F{t`A zZht%`W)ni|lYAJXEnA)=_eS5ly6jRZjDKhK%Rrw4pEsREprJ>Jkqcq?4iYCa`3}Ng zT$!F(>7vJL@J{-~yCX zGwGB%PFLnj`V~I5b5?z$RTz)}TI0jQU4^~96VH#6lpN99+m|OZ)@tUVrz(FNb-ia| z$EJAzq~cc<1SU7wnrR$L{%ZCW2*gvLho>faQR$zuC)^J@VVi@)lMfq~hw>aKdzOZP zEo8)Wh%jVnu=W`%P>Lip|7TqHljB&K1SbWL#V4h9XVzh^T6$JuZ^r_ot*Uu25y{Mw z@zZMxC>GS047QiG9a&K&@9Pd&y2_O#-% zsndk_xun)~DICI?5H7x}8zREvm{!#Z#9Q;{>Cr@&SCD7j+A9F{W&hs8f1A?u3cu~> zwRMPKCXt-GPju#d^;z(Q4F}FMZJ;who8)xzeD7Qv=%q#_)B=}D=PK@>jxPP{3f_1D z3LS2v<50^eLo;Vc_Ki>m*=lo#XxTk(t!SH$OQJ!?JJO%(EEW(VEFn6dIK>eyk!N;@ zDZ@t#L}gs7W1{DALDB)Dw3c)a4%J)$LqRj|zh;ui9u!moe*IYAI<74$XQJVjfOVRZ zP*(Wq{dccGAc0!PV<*_qcy2-y#947YZHrGAX+tS2IhC^`EhlS4mr@*8$6j=aEN^FR z#d_BrvnLNzABoY4AG=KUUq-z1tjR;+q$`>Zm9t*f%g+IE9`2Dhkdj4}l<}S8%JMmh@%f@{+_7_y=rjHN;l$*Y+F{J%hz?fw z0vC;pZD(;&o#qk&7$%~A&MAZ2VA>4}b0HO+(Y^J+z922_TbK_lrtPF|jg-F|h-LP- z>8939(UmdM%NOiJ#uMu6JXz{0Uh!g1ZJ3j1!O$y%NxWyak~pFgcx-hM>3i^+;vIAAPMq_D$U}XPA-a%FfcxUt`}cuNUU6d!}k$mo725;DXbA#qQ}ZMJ~z% zkjvv-b*aXpn;usslEcpOWYn?)pcdcYNY3+yXRy5?<0LA(QKOiab5`oRa}Rz_P*e4u zZI1?OwVavAew=Jdy-m&j>uehLKqcYrmsK(kYOh;{#IhFB=8G48(zG?)eq(NI9DSDs zzU}Z_W}K<7U+F0(ZZ3a9^ONpYc{i*jG$$NXDzs~@Vz%AI84dH_&!vgpvP|T)3#vZc zWT(t?TAq82-xOMN{!9q$99}ZZ$KAr#ZglC~u_Y zsD8G-bvPJd6#l-!Z6w|9eSvP_Ezg#^*h8%2P^6IeY{*fS4cW*GT9)A$RCaGg|Rs`$UPSw*viQF z#9|@MvGX+TpSEg>1Zy{o>2`X$>wZk0gasMXJBnNOA8CT^n(U?6;ri0+&rb}MPk_hM z@SQ`FnKD$=GIWEf;@MbD#Y}%3w$T z&97cpX3BC)c(>kOAbUN?nsmD3a~tW`G7}D0jcRJ_7{fb(#_>R@OOu}TVY2(e-c?N~ zEOhxG2fKReD_@%$K6_j7Xwpr7zDykKR}UQKmbvu~4NX+8Je>LiKqtm$fx}X`{cKz7 zSn0Ci7b0=_M3K(i8AMV#APPJj#!iHAMZ26@P&%$1zU?pFN~KK)x?=;i4`LT@zkKf9 zTnUZA)*L({PeAjKM!iiNAxxwGLzg92+hLBiF6N@;&{<0 zzo*R~s5nC%Em@Rvlq2Xp_jo34F4fPuM>KkQ+cSmFykiT3Cx?5Xh5LAB_qvB#kut~^ z>d&H;*cD|t#q#1cJ%60JzQ9_G=!!I%KVW0L;M=JIT?w~28C#ot9$9#Wf~h^D^&)CxjBTOn&sQDK zjZ${aT|f7|+2Lzjt8W=YvKUT9Km+++a&K!P{qAcOL}fW!&bB0}7WZZ0{vyDWx{Ge(FaY+u-U$l?nSp%S>Gm(aX%>tBufX!kYa>{0bS6VbS z2(OhhnX5W>1<(s~LSqY5NT4`@@fMo{U`M(+1jn{;)gDq*E&xI2!pbDkiKv+V37Gf< z$d-3CNn3Lsbgr0X>7D9RZ-q4NBa7Kn0rZG`45vbyI$7i}bfxg*<4K~k&j%0d&j+4# zCf3G164Sufr6t+BVPZWER|_WCX4c%+dg|Rvm2)66#TmJb(JoFy+kw*`7H(t``R$3E z4k3u0xTn}CY>O;ojvlZ)w>B{}Hg1bRCx)LIE4=8+N#r`(7oVkDX3OsghfVI)4bPuZ z{8FR2x5>TgcdN$2uO`VP(W#Qx26MNYnxD7h5a?7N4)2{hcxcon9q8pYig|wc4Zib1;?YwifBoEjo^jr@hxv zp*MW3TZQC-^+b8^9{X|x)mH#vf!kJ!_V%BjIejw{JGp&XYN_?v69Dh?Z`GhGhsiV z&mEiC3r|sy`2N^C> zoV?z6{<&R{+n^#6zN>~PhFXnnzq{7{44(Yx@ET*H)8p^G`_7pG%1?&d=F}U7#BU;u^h7OfOhr=AJ$`af)1ZTp0jkb%AxB7+| zjRl)&=j{K4B?o@g^yvT(W_2=4!k!fb6QH*Q{z8cMq#t7VjThDj#c7Sntf zPYZAP)Ly=0Q3)Eb_$Wq{TOmSy|rFQ7IU#v&eV#}91XS@C~o zdQe%_{l)a4-tlLlc8gZB6tdZH{Y{N^CTT+D^Z7i5^67UsowKC`Zmn-Cu4RuqIX|fs z`>41uknph?B>p0Au8UUbFWy~zJxu>+D>wD3h(WC7SS&k}bWF^Q{^<7r1OjOM5}AdO z)?rW;8un%-j>T~l2;5tXNGctn#Ds7!ttjRO@eo9|<$;@yVo;3zW4c>r8Rj`eb%f(n z8mwTT(1PFOc{ftvLe-0KBB{(9Tl6|lGVk!}DV&GH;=-g(N6Wmw{_W7zVrKdbba^sT zN`bl3)3@GVef-p|c%rk5^%$ykA9hweKI|*LXO#ybqWyC&e%pTmHcl`etz+gBHl=8?5(MYmZmc`|>@w#!Bxzjt+Z%$Be~I zt=RD0pG>RoppiQ7rH?$Ni9cQ+jWKTOI5`lUc5?^MgszDS2QL3%{Kid_lSGFk{Dgx| zC}!SaAGdr97AT9nxAfNpx#uMd5-LqLfyutx=B3WsCa=J?7ep05tqN>wekDxOaq+;N z2{2-;lW2KF?$q5-C=j%Lcr8MT-XW4GSp3F={DOC27)|k6$W5}sILOWrYt)!I@@X`2m4EEsGJk>9bcXR_X5f7`ZCK=`Z*d4>Ugaw#{g28 z9|S*q^>{T{<3_oat=_u2UW^Tx>__W7YM43*>u^X-KB90MJvm0!DW!SVjmtKSxf&=U zGQJP4U70y)u4`pUy6GwkKkkxsIapovG%{J0skK=i5NxbktS5BmTOZFuql)Yro}J(W zwCCHRxLAf`sr5LkR(_}kTzjB%&`TXEwr42+%^me|{uxq+$5Ly$UqUB-q&E_o3<EzS_f1oZCwSi){v(n!fzPIsWoCRhx3!8% z`8E{33?JIvH2|k3jDz!X%S45H$V%*g52%X87LnRWJG*t^a%(ba1o)J?e(Xoj)M`!pg1k;z9 znO2Stv7Fa*{O)3(>hf5!U@=07@QP;>{HRA#>u7a{rbyw(O>V3%bP9+_^m>&+ErnlXRM*oP&w3?UJ*j|)E-fWs zmkexlRjYRe77KTdOo#K72M$sMW(ITJ{59RIJnk7`nhWRrTd#=kemmj#oYX4AiMl^W z#vWuZUy&bR_p|M3r10z&Xq5sX$;v$aw}jmBHUm)u%7ASZS>3c{4_c#=hcdW*nkz5>?$(qZb?iV7$wz7i8_Qw)VxiQIJVZ>wQRbgr(F z9&pz47A7$>z1on=O%3*(bH0OegLNJ`YiwSz=5KW3r2FdMe5UKUNOrqSc^w!OQ{5dc z@HAK`qpBlxk`;$`nSZ?K}P&Z6c86(x0;6m~_xzfJb&u4~ptA4bBF`aqb zl%nn2&>76jtFMWm6$|qMo<5|IK^qfL1|=N{DuQ zdp#>?bgj%)Yv^hoWOT63YWi6xy77277m2v@mrSU zUMhuz4qB8SpjPwpr(N^RAC8a+4Cljqk#QK2tplvrTkHJEW~zn39tYL_H%NbVzHKv& zSLhY!xz2uS43C0c&-ADp7R8u&5m@z5m9N_Cz|?fbgb7OX&}$q8pIvP=P=zV;WFEwb zA-Y2p{__{o{1i>AC0<)yo7!+>4P|bpn|^IfrPK0?Uo+bLhpW8*5k+A$1$6qYzrWkg ziiHXh?Na{@M-A%o;i&Bc#abz1b2R;m{?{cI=bWr9K9%_s>zcz&nKYO8%&*{;3L=Zc zwK4dbMQQ6n57BuYcf0J`Txv^FF||e=v*o}z+(NH1Zam-I_SL|4ph+V65&GItMh$ms zPM&2q%p=HO+otrWWZlHTDM=u&*2!1T&c=H$vuiOk?&Y@lHHGJe?T*gA6|KwBb#mJ8 z;uWovJ}~}7zEBrJzOb{)xH&@>x_hB_CWKJI=U@aYtDdo5)dB}mQO+vPh5@fCKIya|mZ zyuq6_Rd%6DoE0Re(_P{9lKXD;ErGENWPimP9EQc8ihTy3^4%8Jor)mvBrGXDb94a^ zXBMD&DcZT`)e7~W+LY~g9(7Y@R8F4fx|&g6pxZA|E>?6bUA(GxlE*gN_Eg1+=E2vu za@y}N!^`y&fJeHdsXB*SDN!JwSvb8&;el1}=P#Pjy+-ZyuSiUswTYCHcL@1J&G=jQ zF2Q+rFKQAgi@|TYS1kcc(sS^Uml4nFmte!mRVM)sl)iE|bHa(_Pnfpmy|8{$Skoa@ z-X{idLOQ+QpR11q=d7k>*-06B$f&&tDr$Kj?Z=brQ5>w%lsA# zY7O0z0U=??N!MYWZt*0Q{Ey7Y$;IyBkMqnQHpOkO5uYV92gC)1XG+@x%G)Duc$ey) zZujJ>J8~I6wo3FG2rJr^lwiQ|;pP4wotV4Qz=oNT1&P=Nu)8P^_fp?<{giA3>hq{D zb^L|0qm~kn1s8OlhlTJxFtDhz*vC{khVqwM9dyg-lPa$ z>q}3i3@Kh-csZ#ds*?F>Dk(8kk6~vOxEK7fvMOgCAxZkh$XaPs2LgA%59?64oas_8 zHFwo>6~!sVQ;*pc#ff!FunmuAq%Tq}Z@hvgS5_)H-Z`u_{w~;G`OKz~HTTXX*iX-v+;>V`Z?(6l<80*s4d4w`n z#g7z(bI%O%$dNmvqL*N>b!KW0jwPh?#`ReKRVVRCEZuvYvXVfR7qw+szmU@S5;AxW!ANrG^&9^f!v@Y&&EnH5VD- z*XI_ow}BfqIdLM$#>@u^c7DvX=V>L0`6$=M)-vv~ydji|&3S z3*`}rGIATOf{E$e z=Dh^DJ?f*+s$9a!6;w#%y``)qpD7_DNjANjo;>F-w&F_lc45>)~!n z=&$8rK=X7V3{3VKoR){ksX82UzK@ddv1gI9lo9}gkfV*l=FM~d0{o^ou5p%X18Zb{ z$F$Wh;&bM+>Q;)K(~H1_4WpC6SeQ-XudF<1o9>_K%K-sfq{`|T@_{l{s-%EAA1@0_ zGqKj_F)w@08Cqmzx|VUk{;ZSo5h1jXI#Ktrf_laM#ORj4vZ*b%o@7t$mew@^#8@6A zpt|{d;3W%wCBxzO0waeLyYq~!VoSKn)xeLw`iWi+^Pbj^l>(d}J**O^HCL4pzi?_; zVrqZ z<24VrUhlZ{wT-Adl@>s?&J5xj^1h5EX!?#Nhn9EFnTJS6WK}wx2FaW@m=&>DV zEKOWo6X-P)9SXruU@yG(Ju)>?2b>rA1jWaI^YYT5U3lCDZdP#oQHn0G+}dJS#RgiW zaP=k+?iRL*0$oSa2CJxzK!#2oH)Qa=nW&xY$9z_l7kyC)MG5>6_ZQ=4n62^H^l}(k zs$aubQYV)lE#qJGXXIAb<@n3cyE>02B&_jO6cSYdo0ljk4|Xm-5078Z^v##l!Fwdo zuSZugjQuFDNRlMU5VH(XW?QOS#mH-+%sZ)Ybo?Uw@*|l^RII@E3~Tj6yBICzGwoU& zgV|1(W4mnOWQAPpd`ty87B)v87D!%}aqvYAYm{2ru%w^7(Jlf7vqGqA{JEs1RTU?| z=HPpxagxn;xa#x`#ULBAC^R-fZ6Y)Y3neN@n(IWKlsA7FPVem|-Lih@;6-`CX0y*q z=YG^Ci)C)4jm?mZalt4gr>yx)?R#}(Vv?>}sGc^sG+irZI>cVJx)|McW1QYmj*_N$i#hHLlL zZwVSd)+8AGN~(2|Wagk)VmUStbJ&R5zZ@p4Ub2}<27;aVEY0w;WzSa`E6V&ueHb)X ze$4iV7F|7GGU?VY7oHL(`vxROMNb~sY1|gPDMp${Z?V66V%%CKKGUea(s||I%bmjf zjTBq%@&581uY;5Z!no{+!NWyWM)ll&h)&6}?5ACXQdSuKo5Mb((*8@e*8Qw*RF^_B z{ruJ~UkW6yqMabQ>PB?1$*)PXkkKs@{|{L*pk#O_+BD0sW8}N%0<*pz4VihK^7VO- zol=$hEiOeXEHIViC#Ns2&K0m8xp_@~C{}$G!Zu+JszZDo=?pim)C)&*m$JPXa_=k= zK66Czbc<-Mt;Xy0!q$_&b9?N|CafYaR8Gz-$MObLmd=Kn@^xEl(HHTc^iZxbGX26- zPdobGsn*ZEh-h}LZ^|OM6k7=B4Ee0zb-Z%dCwSi?d*5bQI_My~`fVd2*6au-SL;)k z#?5(U6)WP(#hO~+Fh~TcBf*@%x_r|rtA+Cl@!K(@wE~sy8g4h7$r?M?0{La%r-+v= zZwYaHX_bzx*rGlq7tKO_KLr1lCGq~UJ95(vkfe~4WUs4mDX->R%`y}2;9;F79R$eU z0yrduBg;=YVtA+YbtWNB5q-KJObha-w@PWKYGh*5g2T zG2v)n0E-ql{t-FfK|+*{vdbOH9;`-FARgzmZ#U28B6))=GvwTlM*|BEKRxJf9f;9! zs@iJelg$x0YdR%*bLPFe%kqa+VA=_n-kuA0R%h)FcE-_n3#os{IVq~SG<~?DPZ(cH z{}&I&cIM+=l$KNC0bSERqAPLd%l1+fy}gr7&HYK^A>06caeb*oz;Jb})PCf9>C^oD`ZmqbaIX4mTQAQBoxXPov(w`rZ`+rMFvA= zfa{@XHT=zrB85JhW{xley_|l4BFDj(#OKUzhl+*keK<0!d_HnDiQ#90zZIIZi~(0Z zcF`iqQI*@9Y_p>q5qFNo-9)6~5)wF&80XxMPse4ou0@YK#~A93lbo7AI9A5>50kmwJtGZq{KfZSXsy)&}VmdOr0Re2X zD#_d+Ve6GAz0IDK=@kr7x&@;oK4tgMaFU!EHEuAf4=behAG#D?=97WW`%{vH4(e4P zGr(N$v2-fg7RX;fvzt(fK4Ty^_C#Plxll#u~SZr87E_iLF3 zu4Pa&`c6_I=&iDEQVdxJcI8pp6jT_sL$&|xw$7B)Q0_~h>@N4;XbMi4Qig<>c)(}{ zIw&sR8}B({q3@2ypf2pfD^d=0U)B~Z2S5%7+iL3}FUhB;DjO5z2j^de%CPxBYmgdB z$s=^dgvX>0?-!}(Xk|1MSeQ5X3$&LGYt81(1qTqsB;9)O93;ZoND);z4U?w*YSE1+ z^m~q7nl{1*p&loz+aM>p7b-H*FYiyvSCTzES?Sar7RPtxvX+#IHK>t#((xs)64Dva zMXH6hofb-&i)3x;)JjYXP19|UJa=6&$dpGWc%I{RNrm0v#moyk!CCQ^p?aN5d>PsH z$DI$XOJ|<=cCIHYS_s+K1ji6E;|-y zkw3cT)WHd3ds-y`YAKy0{I%;poB3-x^-ZZmhV z&x?U~B#lq#ZUKmwRpM^XnAS>YRT7sdrDK(_#*Fwa234)`$(9f0Df%AajTQ69<2fam zl@P0g-aqh-F`BoV9`2qx4a`d+vGWz!HnrWBm^uI;$4fK(`t73zqP8hHbR9KtK z=(2~v-)$h5yb28f#2oi06|XoKZ~Kdg4x{IeVF4K3%%mv8c*Qlbn?IZP@J^7_o%r<1 z9Gc?%BR-HF%&hn$@Y^ zHl0wiDa4I=ZR(ZkMXry!TcE|1W)=bQYcUyIpj z4XX0uA5@{_l^;wDazjQ_rBrMN4bQ{Tthst;8l6L0^T=|z?SWGm!Zm4?#d1N6SiYz=`Z{yJ18Q?Kjn6oQMa?4_5#EQTC|6}hh!=mi^ zeqj|A1rZeiB~?Te5R{Zgk?t-@>6RE^h7gbzkd7gwyJ2WXLVDyG!m zpXb@zOZV4(9P>pT&pFpR*E-ifeu0Fna96Diw^92S7vNOaY2F;#83FRlTr*wVAh0)k ztW_Ak&UReTSERqtA1a&JYgqxTK=SCVU$!PLf<5L+>KJ=je%ptFAPZ?hZ*D!V@$?k9 z+Bkm3@RYA~M_)$JTxd=m%9fQDYHE=8)NT*k zTSF)JLN_*hkb9h~rpkKxOccXg;ynr42a=|)#l*(C z6WAhQ=;_8){+aAi3J8&cTDfDz^fxEHd9NC`cb+JYE2}&6m*G4PX3QGIXiM;dHAqlg zb^ANJM6d}(IgYr6qBH>_^VKYKk(>sE;nNIRK$QQbRpo(1EGrLEE!rmzmyn#6z^dDw z+xF!IiQ(M_Oo~amE*dIo6=q_*N3&v5GmGbR$ zy;M!l+?IH%UiVM6rzfJ%qs@zK3i);;%C_wLbl(&a1Ds^{SzbwX(lwBBzDF0I)eR(t z^5+X05rK4h5#ix9BieR$MGUbU{TBt^6PG^&vR$lG(V2;4WN)+m>USe|3m(R%OLj5~ zoG$uOu3VmA7^SF<8ap9{6R$?K3VxYOVm`O2z&ZQ4YWl%my@Ege)iS@e9 z{blm8f~Fe>^82#%we^c|_Gd1Rhe!8pYFk}IOIuQ~p%b7Xr=pIKwst$Sic`u{>+9a| zu`1#k4!TpF0!z4iI{EmlrBMr~uvZW9t?sx=N0FH-FWut>vE*^y^omSyn8wPu%Nq6U zvn@WkQthElNDa+`J9-K8HW=;lC>K&f3HmT9K~yaE?+3Hw(Qxb2cezuocELdVkEtN@ zmC5N>;8279941!8r9L0At?&0MFg8#0ms$TFar4b)RdOr-{fGgBjxx@!Q|c+!Ey0m| zO@?h|q*(n4J@TGT&MQiN?mz4h4E2Z<`5>}Fg7%V5uYE)8-r-e z#yr!YSIX0^VYJx;rHCWlk6QA%@bPC+396%OJVe!X$WAZ$+ywjns~L+w^B?Kje?~Ii z+3CBK{SWsW0Rp>7W*u!&d`lp=9;K2G7tJRHVv&MXmpf-nt=MHiKgGb|SxDfBpnNs( zzp=v+Uah_a-lnj#tPFjxknehhL}o(f`GR*dqND9GLY!+E45pt=`)7-vk5u4mupuSc zKFuN2hSJauo-+*rtB@)cQ{LmPfd}-0=7#gmoAt%HPC_Fz%EWNery9$AJ5ws3%5iM~ zKC>giajIBL2vv<$a!oA5?q|@K*V7ei9Oo8%dL&KOqCs)jF^=B4Rb{pE=R169Hq6}6%4l8{P zw%_*BFg#O*Z5@P$jhksGEywYKA5@Fm)vh(L)cVXVBiy=$21_dtPi8F_q|R7@ViCnb z!(^?o*TAJqDeNE+TP!D58r#PrRofk>U72-UPL!JoR1sw5F#Z<2`Wo5uPb2`=rh9kOoU2wIeZ=W&GOsVG%_ zzY;rvg}}+CMQjDzND{n;R@1hG12z!H4HishgiyHi#JV}ZQ}^91;K5aGO^QWu-r zs()s{3rGo~FKMd9j9;d5Kzg_w05TL<)^nw7n*^SoDMVqs)0l&C#Acwoiw9%qj zZuW>&$5yZTs7L|}T9^-@H2#)4F6)sd%4QYm+@tY1c1^AX{=tNIk3Is3-!-usZ}Z>7Y}z_J5HzEts1kkI%>wh0}S+RWOgCcHOYR%{{$stb*`fG zI3W$xNAV_+TB9X~JcVHemmL6@hH(+5F2Ly+vnlIU=CG8=l(1T-SseraXzxD$i2u}L zP?~pjAIay5`udImte1_>0fXh3t5fDd+x69)Ma>|S>bA1zZDSpR<85I)i!wMxx?+#B zD}J&l^64Z==uPw{)6A|^Ol?2fh8`!Kx-96>aD({Fp@+MvV9`5i`%rk|pVgzng5^S8 zkxBB%<#{!)whB(G@A)cn=MH1Pr_Kd&PVEdY8N1<9YaHH)1;CopX#oS|agKUcYdvYqPy*^@_ z?KtLL^38q!R8~g_)f1fbFk0uXRuQv$;Rjv2l7~dgbewdWR$R}eWeSMZJ=i&AXHi(Y z*<)|b2T&#*Zs|CH3&mBz6G_G|9F;2K@?=T&FPIngX(w7t`xJDUkc?>{vuA4r4;EjB zEcz6x`&k}`bQ5{ZpVEO5CQV?@pP0p!!dvLpp&)5wkoamu5NmRyH~$R}=r$I{r6I## ziLl*v=tNbB<_}dn;8*?pv6+fl`k0r;SlFMfnV^BgOE&vXGcTu3Z%A74E_iXPj?XaU zZR8eY-X|j{0?vCYeiG4$kRO-933DJlK1F;tZlu1cF%J@=2m=A$9#JC;692n5l zP7n39iwga`FGp+baUn8?Y2K(~IrV_AtDSxMt8^8n3nO}a4o zVlL%*k6l+E>bM!GtHk+vt$^1P!(%+0NjAYwcnO8ljRpvefh`8K=v= za&^UWNyTFLuAcHZdRHn5FIm?2%9PUja?E$PB<)sA5+Hg7ocIiVgKd)y8JzWzUT7!1 zNa0K~#jiP!B6wbdv|hnpIIgE;Hxp~#m0QzLivW;jBqIxP!RB=9b0kBNkj)+ zhgr6DginvmwlAWbOLV;1xIQaXr->F~>fSffb1X?bVy0BpEasPmc6S9-F%iSx_TM&d z=!kTe44lUAPQf^`D^so3KJD_ z7ZS*s_0EA3Q)f{!O>B#2>L~W$JA$sfq?(}8hN zv~MJLa1m>mt;s7J3s&o2x5BcUe5`W2>o)@fI}w-;Ci>kl@} z9Yb|uIrKAP{eW)vJ@ehwUFV^hvxru?Vfb^>gbc@!1NF0|Q4Y|d=+B);EafI(RVI#) zcMUPT%T&sS2mO-Fw+P5;q`mx0ja})1T~~IzQ~!GMv?%D1CycJ8qIbs4mn54+Dn`Yv z_rkI@xUD9e{hXl#4KuR5%iav=PoHZPa=cL4FE}S>n7f@I5QOt|&HhWJI+4kDqHnXE z*IrH$5FSw92I3*u#Za1-y_#w??;}i3ZoyVYc?iOi=itm|N872-c?y=zdGynY>7vxB z-g3mYJWt5v6gG);t&5d(C(PF8o%51v6Jl=e`FN0J2C&8WZEr+Eb z(gPh~DADM*mpyy)DNK&;r&ZYS6RJY_)ars;eOn7O&^O~-yB3Oeq@`5hWIuNc$wuw8 zkmOq;=}vK)u3AEi=PGC=X)b&R&`XuOBW9xwkgt)^acj$W zwH-E8GwbdHr&&WAVry>jS}9trO2)nM7K6_mOqkC?^>+I~j|^u1$(_Yg+yP7hPo2%g ztxYO~l|x0s3L%!`ew`h{XKT3&+*i$;p8O&^ndn`otla%BN@LaSoMcEWL!Wx*_~@{8IAOQ zt<}kbI5F(kvzRm}U*ugXoLJ@7o1fJ4rKEQ~;QwBr^3#c>C+L-?kx@`n}_F$B(YQ6dYm^F*eB|(CDWf8FLVEeL z9Z$|-@hQp?vFp`b&3sIer(P-|T9I{SVqNtq-1T-$(e`( zMV}vJDI=bAFo8uKYqSKm0gmFZntW(EZogJxJycy~a_K~EM9{sp+mAeAXbh^&2S9Ir z*vEV_mOID8@A7ZBVe1?ZIhB4A@KCPSGym8Po}wsj|1jXLQ!T~09;b~1-GMga_b+{9 zM#j?-ZaeY1>$M?Qjgzw)FhjW`P9FNZWRwsTq-Xn+kYC)Kig^XTB{2k6Zylmry2=x3 z*FnW9^eZg&WAzII)6R-yQ}%VL*Sh#N0A4DV%`h(BZtBwNazqf3h0I;cKJKY9h&P2M zpJH4>X<i1jB; zt9mq60*8r4lpv}WWZ}Fs`f%yTWPpEn&btt811l*LODj1W%_5eX8pdHK0LK}SMaI~u&a35; za=SYcaL_GnrhTtI!=d0rk-WiykY$w*S-sUt68T86tQ=A?0gAYYrAxkKAE*H`UX^c1 zi-hEcL$q%YrZ2zUxs^i;>9ICTUXDPOLl3dC;K#*d8@MFs~@qx)CaAK zm7I1w@I#YDt&z*gDub?i8=V`Axz^3g8cjLi(Djcu{>ux<%V*_q+m)Vev>*~`%?gZw z_&ueq!+Pxo5t`O@@&(B6WIXEML61ju;Cl>k&jaGKgSj;&UcH-zx^tNY|o*BseC$`W)E~XP@5DlsXh|MqQrUu83FB zylyUVdKBm^khqgr^o?nxXB$+(^e#;J0y~Wf`ft;{-!!UdZBjqhB=8h)mRBklqYVMu z!MYM8BLPzuTDzgXFeui^qL)7x*h~y5_c*Oo1w(Dl5V+{hl5LBOa(9W28O~6DVMXjxf+@k`nJL zpVa9E@^aA7VIU3l(S;Ka>ZStg+`57#Mm?24{$bNv`HsjnRP8>iyY~yxvLDM!g>}T$ zkRng*QHl2J90H@q^0Ay)a(7j$Fa1gCA422fDV*28X0t5plg^COGv2E(8=^0?)z{Oj zISW)t826s*3uIsCSn6iWO9_!5d1fxJK@Y&^V*QIr#q@dOGuK!L4F75ho%P;9UKqUR zGB#7%lp4(asv1(GO0UvI2p!wc27HL=;tQ4#WP{$b(Y($i&UqKT`8uD)vMOakhwpB&&s#Z25Nw)xcNKlgu6P-L3QqY_ zDiIcb*>F1KLn1nuufGLSW*vl?b;8X16x2&$f0+%Hn;nu?uCxS^&oP%G71AY~5ApD2S{Lv2Od6!? z4TAOuV{GjH#L{}pqLB60!FsFr#>fWoBe81aAP07y3by>+vG6|jYhZMYSyOu5Jp`3v zj%JFLH)ZsRPapYBH=TnTCAOC{fD_G_Vpe_TGqmg8`w~}de3m=p z4~sdy!`~L)iW`HsdO+P$9G9p__TseR=#i#*_e_5}tKqckZYUXh4$Qq6QHq^W*yfnt z#if#?p>L)%pK!t~Ee-cXCDs^ruHGN&+J}wmm8Uk2t#28Q^!4N>;vSrgVeM-yV1$aU zGM3nprjuVO1l&V>X{D}bCa|t=I6O_5k?KxeMfUjD+2TckaPhoZiWbJIJruR1>VKX6 zlgsD{tWAEXQ$m`^b}A;4p>xEjb25putz>9y>T0Rz*N#Wm>iVa()L9cktDasg(%Vi7 zGxogUU#5`0`-zYKwFRKHp>dP+70JM+^ zT_F2lFLq6jCe^dmDpMnU4FopV^2M@4Zn;|>Z31U+Y!%#T=D=;z)*OcmFz$~QKR{^n zZTAIt4{OhOQfddW^qz>Sm%UL>Cxm$)xdJG^?KcnH*WRMCJS(@WkV^=&2n2Kfjl=E< zR)essmo5wCKqx5}i-+@J)$!2r_LPufLj2%MIJwnYpw}4=M@7raYk*{}c4r)ZGo;qB zVnj5pqOvr5vaifx@Hx;+Xy@ewLD5VSIV~2sn@oCUMg+^#K+EUS`zni$1VmH2^L7?! z%qAz=IU;Il?0L2anu6_$o#3K6Abxmwt1M-=|`<3Jziy2s-q@ zsYzLl-E@s%LgcT9{dL8hzbV$$^|URIC5aa%x*o4g;$bz?6@@GjW6#Qbs>>c&r>uN^ zLR9-+BW0qsM(Z8FEE!8psqjRW*74zTwQUby-==Z5lTISk%w+JuF?O-^;BFtF91SnM zcT2>*c86m7WYPIuQg)kq`C`5NaW!k%kNIbIT1Nny5xe(v1@!;galOS zjJlj;>-VAcq@(zZ!7+n)$s=zRb+&wb)TD9z70V=YtEQ@@b(SKxnTU>g5oTjkS`SwV z9^S<7dOgaOIbZM7MPv$a61T>vC+^89TmlnFyIpQG`KuZ!;Jox*$W=LBG3?oyWJMO~ zZ;@rgK@Y?M;lFKhK|mcoEIy1OcwRAb`+zbpTIoisrlG&MSxJVwRP9;MPeg|E3HRjpJLo~Ok->B)Ko zOeg!TwoeS{CC%1kbFXKlTju&unNO^+>o6EIY3^h)9=gUFz|BGorD;1QVAwh3jR|XH zPDTLiV2As*wE$U%GV5_?dv_>VMt_RRbyQc^$HNz4&Q{HM99YvFJu{Pc$yZ00idT{D z%A1w@UP3rc@2H)^R0hxGsC)uY%Jkin6~*+D<+nMqBqVQ!(B@xd31SwexfQEylU#G0 zlv(g=Oz(^PYX7JiyUQ;NQ1{SOFOGbv1Av!+j+6=!^1a@A!z3}VdE#hS*V~bQ0M|P` zMup$+3L;*ALxesyNuLzxDt`xnh8XJvzkQU#@Fay|`o`4Meh#tlj3qRGNTLk7I?~%w zP)<0uI#SRW>|p$Y6OTtEil!*_Os49w^G@Kes&iMC4}k2JJ*q`wNCJ~Rau9NBp zlXxL5V6cIpH*B`Cc$NQx_a(R^^;PXB7p_Fy28vO%a|u6Q{8EqHBa9A+InUM^Q>nLr5)dep zJgM56K4tnO(1p6uZj#;^5e3k>e0R0d^9VgrPg2w0Uc4a|3W(yj8~UIA3f_3{{rejh zn1_O&uUr5j1*zU{9#D9H2UOOAaZKrS(m{=S1GJmNY$FUiqap#A_fdMGlEbSaF6ELf zF!kBDMr-$}_h-Y$w0A`*9X^0IJ)d$-iq5YRA;EJVXepo!XSL(AX&ZPpAx>@LgDRreoXc@LES#xiv;dNhJdkyz4AU7bF zC0HKtKw5@&^v&bj48M7A=Pq2m5>ctUnHjiYfN89~s7(*QC&%ltX`U&b5NG;z=|lc+ zP8UduUR{!SQ>3nQNf|CQEoqb?_udTkq&eOE*IVpAC;rQo2;kzIBmF=TiHbB_aw5Db zR`=c~u8^NYYljJb;989PyI0h8Bz_FMwGWM4s~^kOpR&qES>G57GoNR9{dw=g-Gv9r zoq-f}I&p*9u_{N`7ta6Z?ffF!JNxoyL62LM7EEiA#lI_*`PZJ3I8)2KwW;#%^Z#iz z{cjfia`lA?zR`F%oPmprD+~a=)z#Jejjar2E0*>bY6@5i|4hgK_7!}NXnJJ{Ad*@q zfPV4xY5o`fMv{!!UfV>iPtnl=Imu^Y$~L8!;V^eZMFm-dQSR427`z8s_6tB5CLkmv zqM4qOnaMa8Id?`q)&6P(Ydv+7xOm}ZvP91j(YCr$=De+(C>5CO}5v^+4 z{9#p?6>u`*wJZmsO;Go{ckgEZHx`T5>Fk`B z97vZzv2h_GC)X=1zux)nL4q+aFJ53J#k7O{JUdHPW&6E>yxAwt5;U*b=x&**RK)zl zb_%G9N9RdL7wbfC=Vg!A<|f(J^8fB`0^@#g$)emSVd7_{wC$I~Ds4ckLme0kp~L|L zv)k6ru18>TV#tlQMjlV;_?nA{}i;>>y3{M?>qlKxUSBU|Eni{!qIdgKt(4 z4eF81y%xFbK*0}oQ5>j4 zu3Ph;{`PAUY)Qd(lF=}R5*M$wUH89cxRzA0Mk4VIY)_+5!7f`Ny%vZa#BKMeSjcQC zXBrwB&SAeUyc9x>1I2_Hgjwlz_DNp5cfy6XrKML>|6q&XE5gT*e?z0%Ent#BY6jX7 zJF+;auRx>SA{jUe>E?3af`IYUW(h&;20DOcV`oRDV=!!D|X%gbPRZeF?{ohLf?j<^4#h5Y}9{9n)5 z|7rL9`|tkWQ~tfv?f-IN%Ps5LX=`gM5c+lh$)41mzbPguIl+V*E>{%$Y!l*aJ^C9K z!v7vAPoyBdP`5B1M~w$YZH6b30{4%Mi>~5Vz6AgOsw36tvjSbaXU!IwUP3Dq-T2GC z;(rhz-FSTdg5G%}9$8<=8=U|i?a62rlcKT9kq&p3WC`;*I#t^?~Ii>59; zAwCIktc6s5(Jdul07IXgeA6e8VC=gJ{mOUm4-cV#`W7W7u3n09Flk+3;Zp!SpkQu5 zYA0ZrxKT>Lj_X@Jc|ts|J4*8JP4^o+6mn*|wAAYp+vEe`TAm?$bCV6h%6 z5ni7D&j3cuYAP6a_R60ut$SP-7EB*RcKTDid7>ITHcy?c!mr7~!g3@)Zjbnmi2w|d z-aKiXv+$scyQ{rDG&Yc-0|epWn*j@#{%B-Sk8tzZj(yzxaM0D+|87LUZT?wZsCzts z$m)9jD1}Nka1=e*?I(nFG)6vhv(x}Nw(XXisX}?J*kvx`o)3TI2}67lF&n@*eQ)D9 z$$4WT<*2c}Ja@Le+_!MM5ykP1B5yMOr+En27(;R8`e(MEAUR|uQC_2G$7-p}F1|fW zbzVDmLjEKol~cGFl}{A=kC;3q63(1erOm?1dJHTzC-VyF`f^YpG}SB~^NA~0*bFOQ zVA(MbH{r-{&;Ag{cje@7m$UxA#ih%6v)5+xXH~|>Qq!Q|iHEq$u}jKU-ED1a7rMzz z5bpk;n!#EEdJtyANJsalSY`795c_u*CPcN>|JlhcQS_zD`)po$3(YpYajn-pQ2-mc z;fVsCOr~di(mZKjG7Cg9W_j7-?qB&M&QP4>nsamVuf(eUhxkU0@w{fa^FPx@kf0s_ z%T0@6N|P^DSx-~+&x~)g#z2QxuJmMkKQC-HZgSIgK0{IjD(~2I@JG+D{}Na`Kod>1 zU)ri_#x%F?%;}bug$0m>4>F%FoIN})1_nv!S#lp)z9_8ORS{M3=4=Kzp0SL&EwqH6#9{C}}}DwpDgN@D5= z$DVeD&yl}I%&rb&X_)!j|Fhf_H9NKN`?g{C9?JoGUM`2f)G-Gr2IALKWZV?1KPv{R zQp1^G(b8_yTfbzO{kOaR#C&$u)cIRxn2S^Oz$DDdHs{HV)bCp&iunur<8!!!QkxM| zm*myU2@m8xec3M`NRvU8d8+9BzN4j}=LK4(QK@pw#c5Lx2E;jKc%ZZx?tdG=FC^jw zrOxW7tNU&R7VSa%P__4h9T!>pg;#k-j)eYTkkw~{oWPN(zDW7thOgkr=2ec?hGzQT zzN^qP@xBYf`(TT0i^}l;!dT71;g86Wj1B-`!RCs^xtm-Y( zb^SN5qRV+^Tm-LQuU&2i4KF%Oi^^|Lg&z26d?J0W3&H0ajBvev!wcpgsop(*aDi|? zan+jovx@yGABOlhy^p>GpLI>%PL7#h1;cssYWl@>a*v1jU2*K9wk38)+MN+kXdGk& zhLVha%Vg;DPjF*_RxKno#o)%!d#L$znr~MIf%+pWtpdVAJ(kVc;&&P>XTuG61bR^> z+Ie;GGcP2R>^W`z3z}!mk`<-4&d%_1^AU02Wf{SsiZCI!&MZIOMPAz2ltp#`Abux6 zNk3#@n3%R3$~gqyA{BcBAO9Us|MIB`nt|t=*%GPkDDMF%KY)9WY>Ob(S@#B_kWN+K zJnhaPyR);iZGf9hf6D$HnM88JI5x(0v!;3*L7ooXHoLhQz`Fh&mm0VcxUM?N1G%MH zKbq^CnRmxrFF}0lynwv?rmRp`ASrWb;k+x2jvIRqxlWaSI?_X>g8$q5McYAnQHi>P z=+64X)fwiAn>3x#r?kh1gkf0Zp>-EpwNPH#=Q`ybJ1+dczlcG|2-ePw7;}yPmu}#A zRHEu0%WfXbe6mJE*mkBYAxj4{_}-Xg0GK6)iC3SH3?}~EFQRbW3cZwnYnLTI0xCM7 zQeuQ6bBA9g5|Q){AU9qWL8V$+{9b_iH8jZU=ix� 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

7+#mp>mJ6T=trgZ7^s!rg3Zm=fR+`nz7O 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�_h6n*TT!g!U@8?Z|mMVQg4 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 YzVvT?ZtBA90ADw*YFx>>bpOf!1FvR>rT_o{ literal 0 HcmV?d00001 diff --git a/docs/img/Openscan Api System.png b/docs/img/Openscan Api System.png new file mode 100644 index 0000000000000000000000000000000000000000..c290c1c37e8f22809994d72727305c3a0312ebae GIT binary patch literal 194648 zcmeEuXH-+&wl)?lpnytIs#rj}NUu>)5s-jD=tV$!mtG?%pi~vS|-}WKBi&;8jA>NnM>0=J1|I zEcXZMD;ZkK=RRM!t#(T2S*T~pxwljy43?K)zWn%Y>QwX%{woH3kIqr6WPN-5HPl&4 zw?yvpmG{xlsUJP^lPWnSh93-YZEPI;PG0R+BrbalDmao@sLJS61esfZP$(Mb zvLbm{n$?AY;Ta9pZ8n3aHJ2=H?CfrC9M2}9SFEI-Js}q*6bv>~DCQbrh@PpVRAE=r zzjRJza|hB~;;OY&<)O;?^kCf1FWaD_`kY?;<+E4zl{!>)_EkGh$4B#M%-?-c%8-5J z8Lzwa+iO%0%HK!}MWwyy=l;I^`0C8*yQeT$mfs{Mb}QE~S%_Uzi7qKnygDu7^_HwB zcE>FKtLj^PzZuV2+|_Rkv@!zZp4lCABF{T;@g0>De1_*4!hIpXewuyGKKb47-A&Mw zRO*KB`>No?6L-O4QqP&Bx-PZ4ZHwcj74K}ww(ho|(r>KAV@}-~Ifwe$vvw9{qw1d= zIC1xMluQ@<_lNYz{s(^YDLsipJ*u%UGv@{G^auL6=1$)clM(bYxpFoh7bdbn$TS?% z45qWc1Rbe=CIm}MVLx{nbh7&)CGea_w$1(M1}RQ7!iR@fJ5!{4GqLzObNCPZt`gKw z{;Gh9qSOUH8Qb~rA3Zn2E?kuDdPa>5VYqbu_OX_4@$^(F`^yhU?i*ldn9kqPe##T` zYFtt=Mrg*UCwpXMScd9yeZ5X`N5JB*j2AgWwwAjqR5cL~A3m(;ZVN3xVt3o0rz!1K zU-hxu^T*sI;sg?&Xtej1ahR1GH+{L0d_2FUu7pqiq|KLy(x+mdv3ylsyCr>6_?c8Ilbb{!qst4C zFH%#&H7A45#(ktxJ96*drS}C|XBcUjLL-&0f4r76kaI;>CXW>o;c%z&)9Ocmt*<#f zc7my)*&hmfT^6W3ucbzrg`33)-_id_|B0X%87<1E19SlTpm0paskGG7PxWaB7e;dK`n+G1&?yH zq>J1jAM~fiR1==uv3$e)=JV&6&&r?ipCdjKp4=LFw|p=5s_k2;w~pUfzW02CG4e$Y zMmpWgw9K?ju>3T`(JrNS{Q8CK*~s%*>{(B<;?=6(g(sZ;k^Mu^I(EZhj(-mIgOH#C zxxrt>&&_Yof1O`NwvLIb^P1P_In*xLU#q=}{NC=bArh*vK6e*%Z-q8=6m+ zA?b^IvBvX!hV%;e-9O`b=IEKvCx~Zc&vcx{hQA5l4c`jajCpzg%%{7b3hz7LzuK|a zfsgixag5<(JHr!2UR&`HP)w%WesTreGI+3JAQhn2&I}T5Sy63N7^S?LmUSjH7e<< zx~pBvg2fQN;PLb>k5NouwR^?6QT7r)r+f!CyENA^OJa9-YpkQbwNFo`6V4W9i*}+6 zwje`rC1cYeSKHk)XZ;b92v=}7_&i(|&TVUj@x^3x6D~PKYu!IRn@8KxK6bq)YhbQC zKi)BW>GTk2qF`KmBDUsicUI3_kHXFBo{8R!*`}UG#Pj)cxYD6d2-KJOS;Ljx@y>5i z7_VN(XZGC>EjX>4)~nVNeWiTkN=*#&4KP7kie$xmiX2UOO>deKntJxnklyY)knZnu zko5Op^k0r2r*@*N=^3>47?D_ha~tyo5|R}9;Lh}&ggZUYg+i~l2;ryvLhw~T18$+8 zG}#5~oH}I@;dI)MRgK=1d2i|Ha%e9#gw`y4Bz!KsGD4>{DV*yx<>EBQBi40kq@XtI z2y+%=<&8WZ2f9<#8}QG(WVTYH($L{~X%iFO@s^stuqN7Sp=I-5H92mN2$p*@ z%k4vnc0%WbWy*E!`E0S@eIEvT?!Vb7PGa+sOMxL`g%4Ak5}7NTm4aG@_f9qwlP0Bf4eYTkGH_3Q|yPpwxMrAWDluJ%>*mbycI^bOK zf`6%oyWjfId6(~7FehlFX966Czl6E((PAb4f(Ii_tQD?B)dNQ%2u%hsu zG04fdZpElpc{g*VZ3Vfaz|_LjB^T}EG`e1StF|ovM}1x}yu16X9Kw6udtzjzvfd?b zHFHM6e4A!laTwEFTsm4&7YOo(4w92jex$3Q{M@OXe?QnV^Bhh0if)UYPA)LFqc(f~ zW%nDTQH`FFoTq1B-xwk=R$|C-U?m?u$ij6Jk%cfPou{Z1UcJBI%zKG<37nEXI&$0o zhQkY1eeqcF>rz|d2uYtzI{AsgnL*@Q{#J2>(OIMX?i?7Og1Wr1oA-=KcWobj2LF}i zt?*{0kaIww==@BhI<2sm30+;_RwOzhYUSs)hyFxym6=DQ_nz#mKk6);Ek=sNRdRoR z$5=T+M`czBW8773>Qe35y&@lgz;}n$EBX|VTcPXwB%1Tf<;sXHqkSMMbli6KT!fnkUjP z(D$DiJzGV$dv+qYat%UpTwhwBZUyO*hj%-cyK+H|FitqoF~?50p<)9Y4ZKMGcgW0B^-3Y=4*UAGtww>>t;UQc*?NQyu^F9smvh zV!S9d#RX(10Q16b8vZ^ZZsblrj+S?R5=24KyCgTDdw4TRd^Kv=;VtKDe8TO5Rr% zC^}nvT3q&Zc5?BM^;Nj?k2_?6^1)@1E0_Oqi>IT)6$1^e%gU}#tuIRo-xR)iMUm$6 z<;(IKgRD|ViqZn7dGK0ZFeK4QYIPi;j+Wn^STZr&2PbxR1iL&(F= z#nZx9$i?I8AC3Ig&RuH{tEcvEp7yRTmk-*tuypnERJd~GprgNEe~i=G*Z!Y9xp@5f zSil2{9DE}pDtuGq@3w)e@&{LCwd{SZoeb~VI|FkD^r0v!DkU!ej|%_aSO4ttzf?8& zXVqKMGXJgWfBEWPt3o`ipDMdL1D$#*{&T_p?DxNZ`DaCWk%MRdFLUw7ME~O|Fw=@O z@*;n)nj+1oKClb0knHw%HFbecK+FzaN7aEpH~#np%14F~A8dnrd;sBY+@M zUQS&8O+gGrP5x$$w!ID1yw@11%;we3}acpr8}~7 zpG|Fw&$*1VTu65r??EgczIMnZ&8Z0=bQg1DD~-TU-zZm00p@fUOL|W=7Zo>uwas+( z-`Ey8{A-&iO@wg$^7*b*iKsizPqf>lIqP{n`()35Sz1A(mW|MU9L`3-^O;U0i(1VT zYwjyd22eijIIXdW`mViB_UM+eTyGz{SAUN=Qb=;_a-yOCccjkp1v}lN$`QdAiuZ|K zP8tHm6@h}KJ0D{lf|!*LH@Xl$Cg1rKTT29oPxPZt_pNohZgmQ-uYILQ@T582%F>Z* z&OE9iE3IJB`ofpc%+@fPu3MiW2R@kgcp&aNDbJh?Z8EU?KwaK*kHu7aki6p1 zmj}r=y3|Z0M_iP%acUP06FJ%@kN;^}THH`tp>V|wtu)_N;pkFDHB(0KO2|p9n!4z^J{UY zcAuox#GFTA%Du?yq zm$kDGx{sGucu4wv@0-?f4p~n2tM{V|qy-^njeC)Jf$c1YjmBdX#_Dlz*LJd6%+kI~ z%&;G#@M@Ty`my%i;mjm+mg1&uJ#Xo!Z4`&b=6f^=(fGo9QSlz$|(hT1@83mx<8J);J}W` z!gKanqZRVDK=?T5JzPF<@M2Aox00bU4Uc&+)=+fJ$vJP6-ESGYK5vZEv(H~@IY!tn zy!b6>FNG52AyV_P2&H=|>F9R^yO)-pM7^5I_ZDXoE?-nW_;ymGWuH>Q%J*6_lR{7I zfW9$bc#xcYw<50W`^E!q0;4yKc}x>TV$Z1Kd=z+6a?-6)aL1I@PIKTV7a4!S-3m=^ zzn=ADG|9}@Xx-O;`P~US*SaEG!Sc=XD630FGBMN+0js!uK_9F1A>mJU)0YdH^5<5@s^bncPP|C<_|rF`{q|!|;x(?&Yv#*mym^S{(%_d?2;Se3 zUhk)!nbS9eS$35xQ+WBom#seq5e#3729hdbk4 zf^7Pm^7$XFom;RQe%bBrL!|9!lI+UlR8rkm>=E~+m1AW~>7K0?rCW(<{k~nNFYw$& z1kuM7H(yGOsa@Rguu4eK&vD=XDMqz8-)_b>DKh@SoUY^zsbG zF16MP?sTm8-+w<2*D+ZC!da;w0Di%DCb>1tYh-)W3bO!cRY-W^i6i-*`%m5a{CHLJ z{7vlDLzXyvRl2YGJ2x*4RORfuf)H9qwINRU-g?flQ|6r&cb zNO&L<(Ocmo61g~0r4qq0`}#Q{-#gff7ThzKt#PIS6CTNpbBKb4hEM7fVop*vlbph> zV&!jmk6Pp#*|P0?xw_t_L;2a^xil)8|2}xAk-j4)h-mAK-51x)5($i zz68I?Glnxbriwu{iZ=uL?Qz?EmH5n&Vb6^i(rUDBSOe0#_Xx3VGGN6hYpJxEEVSBy z3Arfm`=C_8l;Y!9%*jC*zpiIuQEM5{gd&w(m~AA0B7u3k=F+hJwq~_ME)u+(j4p2W zPIr44w@iRe9fQUO;D|b+x|tn<+NY45?hE8na-HKG{NbQ&-rB@cBwOCk6wMObh>R;? zOST!6i!A(;{=^As(sJfV$tOA-v&T=-fX$}8Y<@zfY&D<|r3+Ms>`*p?fj{_R*fdos zw72WEdZcX`V7XYx%!QYTJ|hD2Y1NBRUzE()@-SbTEn&j@WkrX&2=0PN)27kjHe!}8 z#<_a3!Z*W~2KszeP#*Eiq^Yn0xlyVzy{{o@)I1DH!jN0lDX~$)jXZLgDVK~q6A_7Q zpOwrb^z9rzljbW?SFst=%dd+@Wad8XvC-fN6JZ2-XKa)pg;1PCt4Ppw*TA?|&ULu^ zTEuSTt;7>!ZCN&+9}4M*q!_)I7jK;OUuwS^C$Ig@K~H#bx0yl`sRAzf&L!yIs@5ws zYJX0zctc*{?BqwD3+Z6)KF6Z^%KK&U6yLkd%Z?Lu5J{VzwKwZ)Fxa=FUJ@DaLX)ev zR!p;qUDcB@jFLt6V_q2N*>7Lza)wF8EM~8Cz|A?9<_-<`Do2_DcNW2IK(`53OQ*x#SsFonXLcuvTnOt%*Kc`~iE%w~J&Ln6!*oHMY%=yKWq z{t+KD^Fkvx)8{BY_S)q#^>%vDzCpgqt^~?m)+a_nFBaECR78rw`n`X=QfW&}a~03E z(CMeZRXF21iyK7Ku2KgDm1f+Sm8nwv+s#}dIfg-y4krdHJzs?0o%9cRkm)ZO6UR(L8~60QJ134nyId>`fF@cwrkGxw zPCT?&ykgjUJ{Qr(!$xWHP50hj^44XJ)F6~=bGH#U1d8>sJ@mo#^Ld8UyQzcm9j4yj zQ%dJq^MAn~Zit0B#j9#euLXu~HXxJunE|9b$@rJu99d7zFFsERCKXzm?}GP?`|+)) zPtG5ZPqC7p*TTZeyXy-q-^*|%9qngf%0wX5g)*hua~^%Ns?Q*F#Y*jvWY?z=u=}Y3 zXtn1k3SnH`ebg}Nm#IWBr=LOfFtEyHXCeOZLDpyFRVRfP^Ab2jV5mb{ zS+S(ses=y}&r1DNLOIiaT zXXZ%ygj$(ZE?t@AhDCmXwuB^&GmqwH53sxR{(=tvD?jXlaxsbK!w@rM6m^G23s;Hz zAW`Iw;i!)N6enFW>PHE}eO++hQ5_i7QLdZ(`kenHO6OFgGU7)<6yBpp5x-+Vtj>p| z1pNAj`>2mwnuYZ=X=OETUWT-;xVvGH**WTNvea|colRgE~HW@-=oNELPu|8|GgU3=mxikt3+?zZ!{LT$(v`Q)`vnzWFKxSmlYu>_i=a5e0+j@{OC zdeEyS+JwwzvW>wYRO>_N+GH3_pM4jMNoj+gj4f_|?3O1!QN(b~ERW%r>C;#3W$RgY z#>5H189s4LiRO|AFqT#cw3h@gcgL#G{2)&gnguwMCN&*?_Lv9ud9QCn$!sKC9w!ik zLY=);t!qdWi344L16)}W@ za(A+xF&0hQ7H^l@)rXad@W>nHoH1kVXd=cc_IbDLmZQ|4qjm$a9=mgiu~~^Gf=KjB zh@KfY04X?4>o`nJlxgbVixXHfnK-Gk4q(cA_QRxg5IO|bQ;TIv2F(yI+b6SBe!%iN*ZeJ1A@OM|or zmMaD@96jL8!)kWz1I@lWpJtoz$u`A0QPwY-Rf6Ap#5&1$F&AF8-iq?FO#668gGahi zAFdqvq3h|J#_eI5IxgwbItM@VJf}MK2vTQS6e4RN1>ezBJq{6#P9dN@C$bE!$M&Tu z%l%s~`5~tG?ah=t8C=Czy)2DKMiw<{2$7Qsd7_wE2V<)&wNY%t-U+x)7FO52F1xV8 z-+4Eg3%bX*4O$y!g_-6h253?gAz%RX?(JClw{4HPn9Zeo-u%`1p)a?y?Z@{4S*ZOD zdW-p42ij7&sGLi)HG4pR%kJ6{3zq=%_hWkUIvCD!~x#@rw7S^uz{ zTT&Bmys~_XD&G1!JhT{5*v4jV!Fv~?MEZU{K;WO@SAQpg0nJ>cvxFz=hb$9N@Q4GLwT`YT9f?MPiap zz)>tLd(87NpUK)N($Iuk0Q2K+HoB$0EUgLOi2a*QBKv0-40b-=@n=Ep63}9$c|Pv9 z&&pwGX#fmXwzI0YXZ%KxUSZ?iOfnK3wlTF?x3n)QEKm4sSt4s%wcl6|0PS*2+8!ry z3FWR@{HmFauONs#!fOL*(lH~d=@Vj^L96Xfft(wfC$n%P zznW%8o&^7U9`RqH$W7F#{oNjDtity2RRQlb7gP+qcoO9R!L536- zUZ>_GIZbk^#!9uEt33tICL~s2h{teErE0@sm6Y95_(#m#(42B!PCuG-qpt__SCOK8K>9!E4(NJ%S(^$Qsz9KedWTGti1j^T~9WutLN}75WU<8wE_3 zbm^tFy+Uv$a!D@O=DnRHB-T3?+{iQ!BzX-*($%jpI5H_!0>olx+HC#@bHro@Z&9WA z#SHozXP0qi(v;p+)3PGQDsT(3BJ0PG3^}iF+v?CWfU{gxREtRw(5_qy%m-Z6J#xA? z@=@uo4cjj`WLJ>%v@N6BROgI7Ag-OtW%i+zfYmJNl>+s$TJ7ZhswVv>ch->Kl>-N43vZgtrrO5^^-XElJU zI3kl9=|i)ZsNd8)o0wn5KRNRLEmy9QKcYLrMhH8JtJ(drYaR4Z`A;$Ek{U2=QA~B!w0qp%U z+k*Qs`4tJ>8IygRMcIYo(i!Ac*dy&MIT!};9ts&`nv4u%S~Tjqys1csY)1c+P*he6LUqC~FFcLE zqNoh%Y6&>eEFL`y*w;^X)3YqPHkvjZ>T{dLqm2}UGthdFTvaS#t)> za=F8Dh71E@3M`k^1cZ~Q*^Mjg9@*Q)I0J%~bi$~Ux9550+)5TAKJR#t4fjrsDmPV% zYb7;Y0Fu+)qYlgUcgg!`Y@eoEIjtNkEi*~NolF4703`BFu!6m1k?5DFe?z3g@ zi2zRJ6lvP`pEmV_cL*%kYeq=F?=*w##VJfL?+}(;t(_Z%6|WM5zMXuxy;Zx|+KT&D zW~gtkB`x7pzm;9;#eK7B@rS_O&O{+RN+38f+@v%Ad=gn$;nqUcfkUL)?G7v(!X|B> zn@!)JJW*}V<~$~>HMj2x!)79@cC*>q?0LB$Ss;#gdKyiY3^^Lu<7A72LSj)TLa%Bx9NC9M)^wQ?+%-Hl*OG4}gf%Ia) zWpdwQ!X%|X@~ZB~Op=G%kJA2R$!fteWT(?P+L%BTHj&nGm(%XEN{xyt_ni|A~R$LbsYDhT#av< zrMY^*^(#p%##nw*x>0J%J#3J)QWS8a9dL&W+!siMNk;rn1^rUWX1Q>TEi9na*QCy4 zgDdV^k@;xiwo3HA`EnH|WUk9gu{++==Ej5eA5 z+gnm!9oU~5#0mF(AhkJ^*=qqD595Z01v<6rFQ-0oq7rfz%-hf04I2aqAhri+N!5g2 zb3R>-;8k;x_B7WA_T`OLB>!}B(zruLPHj4q{1KK>!f(y1I(j_1o8`&d z1WXxuj5Z;i#ktz8y%YY#&_OVgyVB1+Djk%1X$#&zoPiJdb!6 zlk6)k5sKQS_<#A_^zHTJ*VTxOWYZdP-vbE>*+Lz1-bD49KLPHnHo5L} z6m+<-lpXTu+y^X>e1S;NHq4gUz08oD#ztG@k?jDZ7pF)Ttn&i7M7nE# zTmAO6uH7}!JbSRV>EZ@td2H(?E8;ID|Ah2OCGM58&!X<^aKwp8_5&KDoeXf-bz0oJ zI40uaXfpqg)xlumLVsz{&s*wCteW|nZhQ4O=u1=CW%Jo|oll=U;B=*w;N}yWjEGKdE^Rs#iQ=nBi}>c4|1N{M zmU0Kt!3(479+b1YjZStflF8RiUp%kK8= z*i`;Wc&$ndE0fe_qZp^}TA!?`1Aw~GER)3<(A|JrxgvL>*>_DV(B;NeMVoU;X+`zo zVzutj2&o^gOi3s$_O3FliGWp^m<@AiAu1M)9Ue&gH1NV)Cv@!4}K?B|Be zp+S;9G@A4mZ(goK2NO@!4*rz1k3S8DpOkt$^|cQZK9>DMY`hq?T8dMwzX#}G$8;Rv ziN#c-{faH2_S7@b=50N8BBER%cI?S48j%9$)F}l>BL?%lXjy;E5?N) zK1MUxlxNWMVlPt?uz_$d;~Zo$y#*enBz>47P&nyY2nuRvNE>Gk z6*HT6PA*>vz*Rc7zUbr{L~fgn?}Nnr3dY%|8JQyX&WTxP%)!|AP&T(gZ0Wo5RPu zWtR$Srb21U)ISR@1ra8A=q0HP6Im``LC(Z6=uqaS)e%3_6UAxa%Y_xEpd!_FQeRJcEp?7%3BxZdp>z4j{_2UpOxayevKlw6@gpCcyNT-0g4I zW=Enx`)gLq6~;p|@+=upwE|+jL=<8dj4jUqJo!|QVsOOWy|kpN(a8wpns44zm2&*aE2uF`J* zM{sU?xIUo0sXrN(rW7YpINKND5@%?WlGP!jCTA8vTB*U2RjyE*!Jilh6b{V3r3DwB z|EPf<;FVSg-1)Z9yZ)Zfu=`q1hG-~?jfEe~rT9G{7xxT^yR{uu!TS>NxI2%!6Ux5k%d z#PT-hPF54RE&?pJrnm3tca`?}G?&*aN#Txv#RYfy3?a)6PXqkcs-$yD;s>|mY|X!= z`kQ(ByT@Xg(wm5rC!_AHZ{#gxEd}rNXto_tv#C|1Ged;W02t2`K>MSZ27-A}s^7(G zvsV_b(nV+ZjLQa-{dAq0JKMAZX^`lEA+I-zozIniwC*)yI+9e|1i(~HoKc6?(ia&w%6R|*2KA<;wlW809E9&%@`Aq!~GF@e?<{H z&ztEV+^~amGz_450e|e9wbDQvfY5;kNG{1v4=Y>NJ4<$M!>}<_VP{T+2UdSTqPGEYsRW0`jxZyvhF%- z?s`RhY^2vzk!gYh(Twi*iX@uQ*S^X%X|WBBeptAlhB zCJZ=a8!x=bQA{Rw6+qJpC0|{LgBg_8ZN50*tU37uzrPO*DA5uM>XyOeAOnAu!Vv%x zOskym8Kmb9OSI^k1-RYtIv+dML3_?wS%(~a)GXMaW zbfEc)g9wmh(dHz8=W@#tcQUMcCVYN$%z>I%_q;sic6J2EGe`F(dIb`Vo~{=V-=Rbz zp^a;5Rp`Q{$vz;)G!l%B3!#@kKV;(LIH+qhP$%4iMK+)OzCTG0qG8%JP6Tt!K?nfK zNtz5u{R~(v``&}V?@M(N`NlOOy6%UUIy&MB=>3}F=f}RI*?)*k0=4WR4gUAjf$Qxo zeSj-~eQJ)vkD}(n+15U4C`y|;NICT99HicKsy4|OC+`W4+U+mlv4flFdBzNcN z{Efoi)Hs2&YV*#!wluto@^+R~Vy>MaDYQ*bVD+f2Mo72AG2TxWHfO=~Y?AVIo^o|R z@Ct5}Myp!uB>o1+l`^$fb4Gqj@LCJPeT;(ux#VL7Q@0wiPAteaF@i|r#ytUqaS6^D z&&3QnFj0aRGguQ%EK8Lbh`k2EwOOC;MKO!+;JMfLX^WYa>Z%zLNqRaWh20H6IG^wM z3!iB3C_#20;_do5@7qbnqu=~$q=O8nzZ~1{R+_#_+cGQe+S6Gq(4D4l+Rc`&?aM&VbuR5Q=IM{V_PqXs3@6V?%D0(7KIul1WSYuY6>Xcoa`X2=ubyWS zk)$DW5BKc`ZDrF?6c#t;UhZZ2?SBdMJVicA*@*K7wGLB@q zQ#Bs=N$E@7y+OS(k75AB0+~s}lqstyoHC6jSMWEoVHO_nh-408i#1a-`H?t6%Y^Jb zL~D}nhO=5IN@pV$Gel55Tw2$WqcQsLRe_MO+j_f_T@A8olwY8Olf%29pNa3~UOzfB zMp0^9uN36^5ODD#3o3#rB>Ga12$BGhDa7}9?>qc?{ucc5#(O0QTK=(t;$=wQieLNI zTvz+-Rk{ffv&m!fjC)hOQUu?b0Aj|msDW^N6S9YH#pmT$e@{N=NPxaQ;DuO=C2}Jt z?2laFdH50r<%C!ZWu5f9KGIMKph?HPh4f-Shag45ey985B^J#=PhQIU+OR`Zj6i1m zvfOHbT_CBe#FBCk13SOO+*&_pOoQa?CGA|)K4qKaf;A-riA`#0GZpc1kzG;~?}zn_ zxTRf}k$!OtWjM|ei@Ya6$XpiPG+wSWXktW!4sr7g7%CTdmpy1drTw*QQ};&C=JiNj z)qp;4T9$f#YIszJwOo{7nbx@0Da9T|q7%gb1dvSh*2$l!0nd5 z%;9>2n%n`s{F%RRH*s*3lK9G;C-)x&6d3=R;`J85)f-$(ro=w#9$#T`u9SJW4Hta|F0S;y*s>E@|Lp}+eZA+w^w2vs9 zNxqujZEox)n?dY1Ulq#uSc9e%o&1Os@?nbW^ywDV6#<6~rB)&NixWD=gYgNFqwp9} z>Bg18wEKBgaza(EO}mnN6aGH(+e6I*{fF8Ry}~?wLUm(RHI{ac6^+^o*j%)uJ?Hr z#$r_RV|Vw?-p4i|=vQ<;xQBSazATLADm#_+?fL0!%h>!(9=1V%M}nQrT^{rlqJ?}A zrUXR;fW#_kbxn+!r6pAMzl~~tgvdDO+Ke}oFxw{JI zV4+sm-Vnf9K1|r`Y0mL+pLBZsJ?>bYcQlw|j77i=WBMlv)JEYPA1w9}tXjC@r@vla zVML%Cb3TXQm}nf-sQS8-J1iZls`6~`U8`S*W$bW1C0@04V(nZ% zF}Q8Z5K~vZ-#oh-8VAp(SY93!DWLq-#8uM=anM<-DCf^LJr= z+?)I`ud))2t^hW!PYteb&nD+>GM#cJmY&81%{VAZ3B)rV{kmc~mylR|tElNDHl=~m zq7vptENM+;j$@+zfJH72*-v0cL8N^T!HTJ)Z<ogW?lVXJ?G;zxQ&7i#nR4XZn zlUDGixuEGyL(6sV$|l<^kA7PyKq2j!^vE2JgJigT)A@MnUAMijaf+{w1*B``8{{>N zeIRWceh~{}MQsDfzs;kknZ2=ikqt!N)M>{{3F$^De9{uFt#aFfKz@U+N2cEdx* zdOXAjtyQS)uv>%VubB(NN{3ybm&x%cs9TH_C75{F;IqD}<|8W+m2{e5K){gc^Fj2T8Jw|U*maXHM6Cs@n(+yCSk-!p zA*cC3xxyXtGwRqm&7ooi;PbhW?xTz6Jry@fTuPU^#~wbNG}AZXij&{_bq$eqgy`wS z1q96q*AR(%M>Bs-wLW9~R+Ovch252)uGYCXRCIEX^Snp~CM1B!tkmmaqfy*Q_Gb5J zHO!jOX@F&-3Q0vbbbB(Yf0!KT&k4!2_R|PXZR-rN))wew5Qq+l={e=5xz^oD_uhCU z)>@X<#!3xOYqRpaaOmYdc3S+^l;?)<*=JxON`k(TV$p)1cZJi))3-WJdpN1tnhQNC zziPy%_8f&{kq%uz?oFNw%+IBG2U9N6FaFtV1yC&t-oq|Y4wzUV_**qr>ruI#$_kKP z_79yy=2Pc$3Q7Crn$ZV2RF&M7ro0N}K&-iWRL1Y?AM~H^HBiBZOzxDMe-lxfkOA+d2|%9F;Q0HlxtPWO+NUq;z^gI)%4MF1hb zeUN-%bq4I(yfW{;FRH*_R9z~Oc)}ZB>0Ca*<36(f*ETasdOcB5i7MFpu{l+nfhuOCQn}8vZfpZmzXG#OJa;xmg zQUh2q>g;;&NioS0JCc9c109Bq-m99qTSnFBKx@HKmuVw}?&8A_YrL6zDjz=LSK9bYO?*Apge ztCL+P{WjEf_ZtXJpmr~*NwZR1wPO)05Y_s0kX`m?%2m!VC^xSUp6TZ<4DnRHSyW{>J=Z1l8KNW+*daSW z^0Re?%Y>{=MwVAWP3zP6O;!m9-N3yOmURI4&(3O|Ktyl_pI2_?WIi~i0pvu>kH`+0 zcqAAERVG+gNjfiGENY7L&`Wk*V z?t*6HWySr4_((CA(vfiYMO78OWz=t;m%IIy1{fWR5U&Y}Bc3G5@H|XrI5=w}(!Akc zD7Llgad&CQ7HYYHHKT zd9=-Cr8=$=$Os;-NU6-!cl+&OsUOVJ+pG7vn`JVeYaNtX6ApNj*{m;E5&JS43`&-h2;fF^#2iqnj9606cath+{TZ)X~ za&OMTI0t4+E*{p)_|vknDgiUct=sjxa{lQ&FizdLua<|4iT-Uz?M?v`6Rst}dpPm` zd)i~vfN?%jpo`-EOI!Wjb22CQBKCZ`@Y#n$6`%}QSm)u6 z?ENjvHxrJlJx5BcXlAJoNOy??NtzL#@v|E}6{8J>9SKYSBdYit#{OM+-$$cM^8n+F z6+>%EY1N^%q{O>IQGN`vL4%tj6HgS2KDZ1w{D)2TcO(B&cqkVl&zKRBB@mQ7@N1UU z!5Vc&)Eq1qL>Xd`as>aTNM&c-=8OcxfcBBLio%5jA6ij=3>^P{ z6G&jaLXr9vp|?ILRo;fUnu@3f-H(RdT;Y5naPIe}ytm|NSK7UwQpRoC_~+UA){Kr$b;BI}FiXwO$9)?<0Nbp03HqeC4g-G_yfSu+>pviX|1{?@8jrvE zoTG0LRSc&vzTww&t^Gwf_`wLvIjp>V#-KuFK3K}mO|}>i$ZX&YNDIZ0v${#|!9$j+ zV}neo-Iw?OQ^Ws$58RAD^?A*+nT5tWht@brA~>>ro)rekbOC5B=J&uV?xXv!*{@A` znmNmcWuuxApiG5)$wuOs;A7P9^rzR8Ym;-&@7AS6=I9m_6Qp>5?B8 z4IH^-`ik-cLgCWXIq<>3%S>=Qz-DS-nBY2gvZSTmeRkMAkDDcE-~VbE(7tB1UEdXLH(8SU*cq1eZUJx{r-H0-Q1Ik34|qb! zLH7CD?-4Rz9^P;x$A@itTF6;6PWRP$E%7tKZ5IFvSu!IOrC^>cQh%of8{P7o0xy-8;}!tZ zg7pKtSr3;4_?P_t><50;s#2bOASsh%2c!=IP_Uc}OKsm% zmrVOvewO!d`*MgvvI*b}(^!6mM*2?0tc(7tr^!e?_0T%x-h8xOvk^YIx=S~6*z@1- znE%lHJ)p%a?Zt8=Glo<$d(%w6)sdeif z-4;MlKt*H&(k!s0h)VAw8w*tt0t5k(PUtNZAqoNt0wN$CL}?O2=q)BR>77VI3sq_$ zp+^Ww?sD&Qe$RKl=l9(0x%ZzuqA{$zYrboaIp&z-ecL2^M$zwN^NRD)^^5=4>;88P z{Kxe*>508vdKqs`nQ$UB_*J=#hyyt%fd-j+_B65*Ao(8k<&P~7eG=2F9t%x#YI*tk zQJj!<(RnrZ$9o>VsW%?hfp(tRU|fwYk9}6^{ATrO|HL=#52a*h%f*}j6^@!QeW5BQ zsYr{t&#@(upmr2%aLY4j+#GfG*Pn#feOSLM*)*?MX9siK^g%O|YRUmtPT{?njli$3 z$E`pKmwaS|S2J!!z$TJwI(OMVFUdi0YB zV>295`;W!{e+P-N5C8QczTR)!f0UJcUis;?M{```_%E(x1DXW`^PVGV zmcY6?z>ZC7{QANUJ~$d^VCH$iGkYN~x2aF}gFLV;$9;Fs2tUYZ0%-el6wYTw-EaMH z<`VK_zMsV1IDRpRuUE4DeaEJrU$Aos5t*dya66p>)Ipdp9!(!Eeg#92ynx$;`DR_N z|IYzJp^zV5U$%3g^WhYYHvl#O)qlfesoX*N#~3?jybE`v@5Kku_fw)DtyK`?%3Iia zc;eT4zZ{iWMQt8#OSdyES%Vjv*T3{|3?VF1{{LU5?lp{RcF11+#ji?B*@BRga=^XD zh5UswqDp@~v(OoN_^F*3aRGV<-W>OLyZWvxggDjj6W%O3@uM z^Zhf;IUU+Od&S;F2$D82%6Q&yGM>KG{N~WTx1@Omqd?$8Vh+H`_^qTzhN;EYRYf=J zkNsNWb};*UXZ2j_)pQsY14l&+4BvcOxnR+5v@IVKvFHkTNeoM-E5!V+qCOWj#P#$? zJl7nO92WzPI?v}aws+vxlZx=0{Tk%TiMb(V!m1oa$pEVMT369gX;(;-9b4q=1;#!>jd-DdJiC*vPfKS*x#_;rd( zZ;$P1nY9kxH`(+**d=mpOJ9d=1~?GX#=@~O2PhFm8qtHa!)DO9(W2{Rj=T>$V+Y8r zqLTrkS?G4gf^BOo{j?9D9YMzAw4jf1C|-^Xk&*NtwV6S+Ep{XFUXQ-X(Wm+=*A-aTKVB@RVF*0v+eH0$8h@3#v)pHntWn@B7|4}VzK z{g_6wbMKu87r9B6f1fKKbKvg)LlEX}UqiDvqXC!Ij`J3-d@KTAVAY}Q|Gc;armf7f znH^~drF#Tc$2SRAXTA)85l<0q>4vzlliCvVTaI^3PZ=6M^gP%f+FVTGDo|CV&HqrO zY~D!pGmH%ciXK1-y61MG3>JTJ0BBgA!vmWQTvepijS0YgCj;)uJHSk%5Rc_3ylxOt zGp@>^F^8vK^Y4ABA`|QxbO)wzybwmq&$4P>am)$1cCG%$mNk!26ndw2?~ROMoHmb} zY1B}n;Jc&mfera)y>?o(fwC;W!d0;SDwxdsd(N{h=PI`e`3B#09Vm;5C5FT5}5KUkIWW&2tjk99!(L#9GbrSC;Q=C(!J!r4bzwrYxz1G(8HI& zB!u_=^3eRnehFhgx{dEhH}=ulOcDi|Gmk)~>w9?y{fM8R`_j=N?@eov_6TAw^gva^ zPF?u%#H&0?XiBYSJSb+)OKUxEV**jT8($rYaww_j)+v9Q%SHrzfzENN{kWH`YxS?^ z9pE4~pjmv7cC=;RzfUd&7>~oR$1e?NFfL4=6R(f^UH#(Ee%4~9Pk)Kg+FIp6wxNQh zdPlsF5a1ElTihEbZkz}Fn^Hw}esN7A*E5fz8DDQI@IKp?mzzH|%c-_;2Z8KD>Z(8e zoZd?q+ex93fRy!yI(<3O>`5~d+iVXLk#?%!JE6#TsH!wqt1*t;NL63Vb|ksFRrVUy zic@!Q{2Y8iiwSXO0-#4QMjB(r-Ks-GetW-oZ)7}*CzR#9x!r842PRO09quzxJIxU< zqJ{Z66(nZ#ye@FNi|TFIF!+C-N)Y)-U{nuMnoZM(SCq9h4}7}QCME>q^FZ$!+|U~< z(9ft_DS$kUYax@8+-1N|c6YG-uYn0GnP4#_X0bwfn*KbP9IryEQ{jhAtSe|A1gCh! zswomaW3Bd$Vscg(tp^Pol#4V0`j2_T=ymG3c2c!z)ZFHq3YkynDE1XFFvSAf<6@hB zpa(lP4P&&nz{FU1Qpy=evTX5|P;o)A0b?)akTLkg3(M$r|9{HSO*^N5)IYf8x-JKL zBOP_>weB_4%?1Hkx05MT0R%a(Vb+eX7b7IcGsvN;W5@r-2MrhODz5NvvikH7OZHC| z>@q5$LQjkySqpUxkScuteE9G*lO!_wqJLBADF4$VOx(whT)k*=>p{Xrv<_MOLA231 zZf;cADF5NZb7$UOL|5+ib&=Tb%ganSJmJAm0{zA6@4LS3Bk~)Z z`tp)dh24E#4V#zeB+Uj_u&jtv4FQ{C*_Z}DNYAdqW8#X848R~J*jvLO4(oVPeg1AB z1RYv(@k%qAZBK7X!N2K0j5- z^k8ZvtdDV6maHOCla~Gw#O`G2jh(bK==p2!_P3|yEBD}WG~-G!wGib`BBM%`(@8+! zzOzYNsrx=GZnW^R+Z#`)b*#DwhiBPb)p81BpJ{{BR=iFC@H*xOOHvZtx5Rroc} zB4#&&J{h?!ktGqYw)ZMYBIQO^Y4X#K_?$J@;kqs^{~}mDx9muP2$q>$usV#U(Dj_I z^&O7H_~XUGD_8}))jZqUbj+TesB&{=i3UFAVou0GgkxI& zq%U?Cw2aVv5}>N!pzL2XQ-!%JX4dhE@*1`JnK^iumYS^7ae!gW(Ap}Vw&@U(u{+@9 zW9-$$N$I7mB{U@~`5BjlDP2UyI_FYP3S+=WwILk?ll|SSwYZq?o9vU(^){<)Z8}w# zXH^Z9*O|-q)gjzn+OHfLpBBFZN6l4@Px;~3MWDHOlx~W<0ry<#05nOWw3EgaurE~> zB*|R3B%R8=g&AZzpcrc8RGSn%u!`g$dKfMhTJNu`zbQbl8W5L;zd6s}Ch0Kf@w_t4 z=IYQXqEb-mSKjBW$4}q-?Z`j8e7wAY&w+oBj*(FM)cCD3zUo;4LPn_XwP_?rlDnmf z3hKu?Mb6tLZOy(-&meWiSNYQsFXQ;I*YEM|x@|^N;@KI{D>?&A{NF!tXX6S?)T`eW z)oh8KPwJ<<-+ftFz3RJE%Q@cDCk^uqT8KP$a~U1$;5CZ>c*XGjXx-5Y$BD?M-8%|J zGps?xO?5{5!71YTKg`V7cm^!nu&2TW|B7>*4i@Y+F3X;eYhYN(5Wh5Ny(rjTg~NL) z1twC>mrUS)sBd|OpK9)8<+|H-A5uqZntMPA`mK?U`N1HG!);_$-pqCEwVp+DVG z>kgICv{epV;Q5!Syh<+jBA>f9V#2kN&IaHjVyUe&_oj%}9lN?cO{(+ghF3i`TSavw zUZW;#wIReoU#o^LjCB7rTXjG2Zg6|AYsuOazw*?pS=;X+5_)Uy2Ue-n#iNEMJ{0dP zj)wE~fQ43g7#1*o&2Sy98}gYZHWWB-?6eOE`^RniQkF6z%QTafrIS~MJ>=- zTq1q(jW;|nO4bGXA$_iAw@k>=Xfkc2tNJVwbJu%Z_Ia`9HzzrYqn$&uvs$E~`u<~P z&Y(8+OcqI02N8cV(CPK-*A;$5c~o~TBlkSzj^>Mz5b9n}BZDph4DeMH-mDB(VN``i zLfU*0JEap@0Z*?o#6^~>pB^0eF?8PS>YqRG{IhOeEwkB~eT>)$t$38C4kwA+80L|M z?7iW7Hz28q$dGj%?cJY{32DzeMP{mOB%q)Zln6e^f`5piBW`EhWh8(aT2XV=-g4r1 z2G-Apc}V==K#Ik_JDYEdCit29fYX*dh5neMEoLz-{+3X@6T~19Dy6?G=oYmB87%+S zi=%1uiOZ+%k7}E<8xV!vCVZ1L7?0iJ75pbXl@^kQZ8{u8j-C*H{@Xvi+|~JTw|2?C zvkB8rnt-ld%tB$7d+*xLEeI0qo;0RpWNL6Im6KwWj>LL`1ZpSUJ<&`u2T&{eW|*Ej*NiYr7~6-m)oMbdxUE}rqxwWty_H%L9ED# zI-D@1?c!aWu??SsO_Y{0EA5(^rm`^~(wvSL%Du{aaT$B9N?e_?%1sX+7kF}WA|jZb z&497lku!`TRFpnivV)u37p**vO?g4*k zLbAPEE}6E z&4j!Mi}y)`2SiZY9tAOQGUQg9EaG>hTg%IY7aavX7zX_=ue(~+%#^{v=UdN?1d{6a zya64QxyceW8dQo8E;2>B-}G6$060mJv)pzbm(~|F$c7XreC;Tt@!#w4uU%_E)v~Ae zyL&d=rKoQKaDPH8mQU^5BW;bVSMN^+IwtgHP-=V(9CBPrI6qCgnz&J{r(eE2ZvJC0 z_sgiW8G}#dGp;7(92a|by)M|0@`}sug!_6!DDYWzn z>Vx>*YTinO$2l|cg5ob$wK3Nk9=M|sk&i?7^RsCyZ|Deu15*pRe2OPXU5l9$D)=-_ z0aoBzPO6R*$f>P`OqV9FRbu3wMh746DM&dn(LZX(baqdhj`dJ?Rs?@9!|QCWPiQzI zAMLm33>U0p_k8QD$5;(Gs|ERnJ?aMw73>L5e`T_` zs-_rAm9CB4@XXeLVr_|)5y$5;LS3(~WsUbNpJ~k$scxwTHT2(?QL+JM(Sn#Si=_s7 zWfU*W=k2vkJ;x%6dp*ko{`YgA@_E1R@*Mqqwd-9244w(z8ua%9-3s)X^Lm#FN*bSP z7s{mD`h0w2H3t@H?KCHE!~+}8Ze{zS5ssy^5F+?`X6K`}P@Kq{~gXPRub#tN+i4rDz8ypp@U4^|fblz3ZOS&XJ1ZX*p8`-N~Lu{6=_BQj~M z%GVGl4(vWH_Nx=LiZUXlVAl2B9G~k`R(1j;{`frdXEhU3q18Whw5TsYnY5-Xvr&9=S27RF;PZm>$xKu+N?i_nV zMGV?J;FqmKX8M7&2R@F#@%M<1VG`O={BW;Cu{F;!UOlZzDU0ui5Vgqo-|ZUj_68(` z_PMvMT+zk6S4XpqM*+R4M=GaX;_k!cIbrs1mVA07Wsu-BY8tLjjngKz$wkqWU=`lF zu2Qx462jPbMC`1N4X_rc)9S3{QYX+I+q~kYhFQv5Q>iioXqQN5cEf3}4un6Wt~~KO zu(kTi;{1EFC_$awE!H8s)jR8n4vW)uwcP#);}umgMMRIG+HTU@rAO7P+{5LMEC#|g zl>8_{F)(#hvvVcJssnG8vsQ0#z<%aH|IBPU9Wl~aU*OyZMc0VFaXTO&51@Haa!RIn z2WepL!+?1uf*FsZEU&V*FDQ+^+rrGfWM^0cgW7TO)vO<>U!IHfkv6 zX3zPV^GF@LoMOo46%)Iyyee4j4~^hnuVsaM%UZ(Ylaxwx!JYK18DC0DP>lNX4}Aiu z$UqM_l;N>W!&(;y!|jv*1owb_^x})~AZ0;t7)i;!_<=pgRh-7mzHKMq?UQ-nHauWf zpT2?>RcZBI-3s$v{x*^}K>9n!rN{`6MfAdn8fI4vicJ;IJ;%*o*xrOycssr>Q*daZ ztGz3mwwSFS-ORrtD&MbU0;^NHdc~y1A$Mk{W#RtTMEk}O2xIhkp}^iN11IgV_E?J` zagaf}>K&bAWs&cp=cVaiiw6~lE5Br$+^DO8KP|iVY%?NzYd&5h7{`CZyDUipnqDaF zBsvU$44bsFB$8n`Oe=ElBvtSPTda$pDf^@SjL6$deU?v5uet5-JfgSFae&hzMvASk zW~tOF0L2OOZQ4jk`0X$3{PNq6We5Q+UmtoK1yzG&ia=b9AD;&7aBB(+3n{3vBeyH{ ztasz1BUkh+s#m2^#rbJ?Q^fd+hQWG#Y;505p=Ka^FUlhSWPgfdj+>NN=39S50Y%Tj z;^!)wJL=xG{O)zQ&yy=0#1&<}3*U+tRZ4U|29Zbw+(zUtM0u6Yg8l;M6K1VVY1NsN z{0_aI%_{(ze7_V;a`|jl9_D9#?lfV5$6B&HJfCzrIInjnwS}oL2bP=o_-VvZAmX!j zmWRYzX`fzndCf%8B=7T>swQ>i$t<=#tx{06{l zx6I1xvk$Regk5|mu;ZSZp0z4}mJfI*_DjB7YVbYH&I!Q|g!{XxD2;q3f7zBtxp;l% z(jDV`)tf#uqIKIn6}o3e1TJ!l}qq5qLq z$wZBNV#1}4G3Fpn7w(zgiIv(~4Y}jlgy0ft0OHw?*^d6DSD|+VppRKUJ6Zam9rfCr zWg_D!Gg{SaQ8K<0XIeXyn_=uPOv?q8Z%7rA9Hc@Rli#(~?x4fPTxRyiJW}9qQ@$(19(j}=+PX7(k%Ifu zL$@g6{Pcw~#ZvKvemmH_Zr~~UPPGf+5|0^q6R^zec}nC<6|UifcoK~3`&UAWV2M@U zRiRwZzt>h)M40%5JyzlO&xHuy+JUx{INT^Cqfk4-rNZ6HY3iyoL_GvZ8!-KU%YR?g-ekZIi?ykQmQXDf~@iW z6g8aI#n>EA4^eu^-a1~ZUxoabsxFV$pYG7i(IyR+g7!i_Q**`WdPP?71&H=WNzGX0 zRFbK62*E03wQ4h)ZIm>#1DvGdWZLNulb5!v=w+i6hDh}SR@;|3Ei}fx=iNC^FQ%v{ zu5)B%=)XJ5Wxvp^p_=?|*W(s4#d^p1evW!bL2vqgB-(s%iP!;H&H#Y6HU+X_%20>+ zz;N{vhZ83e)|L%l>>?&%C#mLZ4FA`E5>r*wCq`J6peqL@j{_GL_TDO?ISCz%x7_}) zog3t7Zkp=QfHZp)mCQ>~-?iXSHsc=I72(7l=4dNpFd93W!q_+A{f1S7_lC>roF{7B zQpEUVs~vZ9KbBg`gvO=&5s-`I_ZSUjGtRU18KERZ1*UqAN`_T2m^19-)ep*4jj5qB z#6v>iT$NVK@qZ{-ftvP~AlF7zAp{q6tKEWWc#>hQt`e1*lyq62PhE6-sbJxUp&z6XcGBHf*OOx2aNNa?M)f+hMT8+3(@gkF=y}s5D!yOpc!9PZ4|tZnWM?|0@q= zo>L*S`qqO0z4g(Y#Bl`j+j73UbcXgG928svG12SOiLvtxqVBEx-@~urtq2s|G?;k(0 zWudjiW1yp!@1GO-5``Y^sZ=!xFtNh>=>-^T)q)YI2O{KZyx-*XiH+4A zR)1{|t|z#KM+I?-^YH^__f3(NRvvEusy&=dr{zq%TTOzXEK!7H-@Z_qh*_|hO{l$` zqMQ1qNiFAUVu5~k4T@AZ4+<mH;H z3@W__#0Mi2IM+r9u;AW3K5^Vk9rKPplRL$66R>W)rl0Qc?6+mgG~+GDI%>3a`Pgsl ze5x*nOFQbau<0H8$a83O*c1%8I%C_RmlSbwx{@Me=LCaSzyb%<{n~|saOzM`u!KGl zJyel#AJssthu=qAjOZ#kz8dsvoh64>;KW};$xo?y8W zhg-voTMTk(?bjYwZFKXB|2D-!Y;heP;&tRRn7#oxE?I^?P(Qr%MXGsaLp{N4l~cyW z?;FQJBxia$jX8q9TD&|`q*^~+QGc;7xTnOuk%RBs-F{`No}qP7+CAn%!ibU|4E)d{ zJUXpKG&bub; zTn7{1u3{Hr6&9%|wQW8yX0w2}Pz)~yCp#H#NS>5S%$V5$np@(0&wKS{M)U|aY61fS z6(ePzA_etja)%8y05Xt0sLeM<#dY2HK-4?PBh?1rVfZA!J+}dYFZcPvGy%YUJ0rL^ zL{F3QAfv{xhaHD5L0C^We>|cGJ-I8$aiHon`JJ_%N>W+ts~$&zP=2*{3~@Y&8IlLw z6j?5~ya7^#TEiFz$~n{F*dPvi@i@VHbzRkLqkH4U3r?rCs$vGj@3C7g!M{@0BVjMl z{wRD_9ZSI54bEV^apv4vO}qA>N z8_0Pv4!{UAp{CIadpNDZe_dJ36G?0U^a3G!`MXWs4;)XR7ZoNwvuL&Oxis#hjL>?G znD7>yaw_N|k~V0&S4X0_7?8BsZ)Fg@fsS}57F2!sp&-W(TYzzpcEQtm>q!mpb>~V$ zKdj3HeGvnz?ydvzQ)<CG1EWBdq^YbN9t>fn^2H_PXxw zJ(Y4*b{f4G_(k*6hY`Ru!Mo*nA$I@Ny*SzJnLCCi@d-XUJUbW68{^N~VciRlm!Q{j zWBx2Q=`~9pM$b+(3MR!{GU?D}7xh)R7GgbWQ3|Y?R*lG47w&BeCc?BCqsEzgCl^Ae z1b!PHV&+(t0$SXl_smmO&Xuyxlf#vzD876R;ezB#g?a+~ztgu*y;J7`JP@YP!TAqc z7b{?m>XO1AH67JBpxhlhmduzhKy`65bn<(XJ>7ua%x`%Rtdq>faTSuhP?%n`^)l4& z&FjQFG0fFqHA25k9&--MskL>W8F!ET$!P; zp{Kkzp6V4)Pt=Bep7~j-9gwbD0r(iaZUz^Oa;Zlv_)dP9@h$9M&kNcu&h;m*#OI}P zTmbx(H`|GMe*ENM6MU8i*$sY?Xq@Gn4)VjT_K$;WoFw>Kd==29hN|UBsGe+pQxW-- zgd7%4vSSGHvCq1RgWrxL^NyM38EgoX<|ap z-tKX#swF!k4c^YjN!a8hH&z54e2#qPWoo7{JLH+%kRn9|1?{dm#usv;og!wHtA%Qp zI%Pnd7DU(yebYM)988cg13c-& z_hv~x49m>MC+~w3)DSkLpTs|dUTeg>BeZ%*U~7m2EI(3sv5xiubv4`gzBNX4^C^_&2Z6b4}FnW~t#Az-P;@JNUZ z_1lIQ${%slb)Lh3Dg^RGPBu+O0Ziqe3ryjI$sHnE*eNK< z55d4xPEkATPw1lXHz@GHC(Y#z5BOpL< zB#&2<60TT>*dPmXSI&T%+m-#Wm?J8aHDl%ue{G17Q}_H&Z*MhFvQs4SITqi1ULj#Q zm#0hg_jXLKWnIQ+@-D5YP?K7+_j|*z5(*dIYLeY{T)AW)<^6f}bquG_u&AQIWuAJB zMyT}_fDc_d6#%%Jjai>0``X!jbjyg_TFQZE$r-Ay{3-EDNrz7Lh*2FYs>dR^fZ9=) zPp+7}a#DfoT-6$(5M^BKYgN~GQxaQ@2h+x@Is{>V{scm4eZxdC(3Sb2PwgrqHJscxE7;OYF{g$#-esPH;ARtwGPO`T;QmIICibVsTA+PkuQIX~FXu%8ZW zKPksm3@!?$O&%}gx%SMkJuYoo@Y)2{4H4Y|Mo$Rfu$mvwWX63(pE;L_E31jDsT{uO zTtGd^W)Q_T#zctuH(aF-0(eZ5P7jV`%Uwab5L8KZ?^nk9LNZ=tdBKNa! z4?24zU9y7R?kz>RbCuek&-C}80#+~Gx&*5l)U}dl zFLqXUKUSJCMA`jK@OY-65^nKK8xsx;qa9}Uojpr4$@6L3I-29TxA~ngK?&B@={*Q-`$E!%oZ?cjhA7EO$J_Z?M zg`j>s+W~!%)gUVH!#zgn-r5*`YVw(G+rQZ5b&Rkn12efzn(-|XZVaYg7f1#1!%=&93wyi^P2vrIXHHi94xU0(9T*_o3XAb3%ViSqUNrt2o^M zih1dXBlmOqDAGJR?}Xz`bm32&4IF53=XiIVKc-b%CvuPyj|Fp@Ak~n1HqJgM1>r<< z&jX4+>&;bNa$R;--oV#(@DVpDBu~MaOpb7_7uq4zQ%Qh6aGdulN<^6O0G@A`Gml!4E2zY=W4^u$J~^0(CHBA9t<56 zda!Rg)?g3m6iJtNy9}N3vzXa(o2cpGVowixp9PT3W3ZX$hp5o-LJfGw0lAU?`}RQN zyBx&)!EVa5s%@QP)$qa()=T+MnggJ-znD;|wBE7_)1cfKGQ%?D6eku##CxLE~i zW{8gkj%+ug+8rXte#V&CCFl50F7DE$%AvVB^{ODE^+8K?F{{@88u` zOaEk*sG1&B=b*hmPOEvjCR;f$$y~_A#kEj=H>XM{^=7l5PtNM}Fwh5&LU62W05CrE z%;GNqn>g+l3QN83MA?OWTChBi&CI8QJj# zM0x*iKIW@(kKe`0nH`}FF8iH#TDC#*{fmI`*EP)EcGr)H(~Vhl%aoF>Q}KKtB+)}^ z)Xg+GDn@zB~w-*07OO~jhv9z+oo)=iPzh6XRn@V8c0Ktus- z04gzajv}bjf5_n9Rdtz0awf_2E2?QVu5{{q2K}oe>+lD4H(EkEvDA{&gmYK&lnmGU zjBg$(7Pt$SoC`8Fn^zu)G=3=a0?6RbD=Eh1$Ty%n_~LH>X#+x0qZ)65)ql)ff~NR} zmWFdLe`z667y02qkoc-qghPIYdxKTBGohtd8L2tqD+4zZ^1T)wwUZ3+#mjGk>qIe! z-miCvDx*`Di3RN1aP=t2YC-6D)$LMd^=kGDih8|E%W$linc;Y#(DPwG|MhXH?ZpDi zPdz2#ON$~*T!JGI4g1y6rU8HG_0YY{mAzx!qN8wbj2r+Gi8O`hq;0r#iq149+}DUt z&D@%Vrd;#Nsia<5OMcwg`(s?rgRZw!rzZ?(*Luddsg@Zpvoh6)`hmM6_;tSm1AY%$ z_`x9d)#Bk~t}Ww<=o*nDda0eDfdPD`=-WI2jW$h8HSPc~1w_ZA#O-)zbL^JnSrv=|G^a&D5-!6TS`{k6tBEk+i?}BYK(ZzsW45yvZ z9R?Mc&D$Acj_v?W`#v;0EK~yEf}QeqWt^8%6JffkKEssD+=XsNp(6jNVmNU@>nLA# zXm@woWihb{#}HEBQqM$;`a7fiVLFWQo+00ZhE83SVAQr)mFBM8$6BSJNS}G-t|W4a zDrOnD-ufsSn#y8uUneOjrM+{3J0@^1^PTus|JLRG0F&`Pr}O6}e!mn8@90_Sa6C11 zHd9__D0toIlI;Vj;o$8kHG+vjB?<}93g_9+WE5zQS;NfTO`T=`)yLlfnalUwMlZ@M zB7`dV=KO>vzP|qS=P{=QSNiFOIE3boW;j~pAw``qa*IGCtpH`8B-ma-!ENJ%>7QCf zq`kwFS>L@Sp70hUEFcl976Dq*vn?qkud3Ti;hf)o;tKG>!S_VN?m;pMIJvm+kTQM# zr{H0YV`}>=oQz3up z!!BieMOo^@ir&_m3nEORuM{PKLnBLEHfdDOa?ZlQ?RM{p_1aM^S+Yv}c6{0sL;T+& z3E@@|!%Rh!#ev`n64Z28VfLZrY2Q<%0R#kz29HAn+67xfG*IE>z1zeT4735DluF%^ zA+{atwGt-8r4qW-U_lOy#_}a|Rx{1I#N~%2>WdxHXTnV(M%~U944kAfUozu+UncK6 zU^UGTbzBk=t>Dj;hgt8=e!@E%l;8E!c)8IOPU)JD$M3G`Psa-=t1dkYe*tLBbQUp# z#dLIVq2)`>JD|mls}+3aOkr9#D&J3}njbv+-QBGtBUL132)(7#3FrazdsAO$P|i zBwkW0`+T0*IGiwuYPLD^&je`G4Uw`9#QyN0`013SB)c4Pnq!s}IdtQ-T#h7k${Sr` zzFkYO{&4F^np4PjpsW_Q;nL4YcdHaS`WN2eyMBKZZO*Lb-EdCwbmHu@J<+`GMwLhQ z9WRWP@&n!^si=dV%9J{Iq?mhxO)IuW&0Q?^@^o_ASVi#pbH4Y|A4`ok_+`jGo4e|G z3L&ZN{q5~35e3HxJ<$RS6DddY5vkVd-rG%pGkc=z)M1X}33iC{f*lmwU9e!F(MPzF zEMYASOuKIGtn|LSVy&Aqbu5eivn2>t4_W?_#bhH|^|6Mzjsy>^eGH|^`7gLKR#`YM zyalR0im&7OOx(sS7kl=*EEQ-_jf@N*JwrCocNB8dZo@NpA2|O~+%YHXyuAEG20jfr zkiaYR#YA7`qyc%lak|vorTsdRZ~4nXIy^7Hi+t>Nhx)*Ex{=4wlR8uupqU#kHrYOf zH^2|wTDLekRj$t8YMD>bj<9e^wA4yzR?_`U~C^+J!W(|w^ zineL@WzpBS_7B50=;!tNmON;74teqcXe#)dfDk(LcYax+)nO&1f>}MpOK{l}O_W9W znxVW4$*L_I=K_z%BO#_I)jelE0PTG|>u`EO^XN2|FVZQwnh2i2>jm&-c#KL)yPWZ6s$%ptFVz3l+o!}Kn9&w~k%Mjk{e zVtw|m;JdpeLT-G;9wWtQE}Sc_q_=a(bx7d#gD!J&85^iRfKY5{k{h<*mMy#z>7x-B|_Q8MH=9YP7=Rde}b zpc5`8$c1w7 z(Yqx;eU#rpJzf|a6&UN^=FE;kZ?-?HRB?7T3B$A%pUA@Qb&%nCA=IT7clV>#xu8{m z(Ky_o8RnjkT8E9ZEfJn=$MWL5So(cuU!4g*7LMYT_w_scmsk$#Gari|vrW3{Q_St6 zca|M0wZBR{W%GcYtcE>!@MDm-NpAB1Qn?^U6f0sfIkStTcq8fl_dwKYwldIZwe_kB zqmKbzL#uso5bbIV4{UB)?+#EN1|80(vj#9B=1WH{32gIn?uRcO<)AZtl}+j8@k4C& z9o(-f!$Tmr62F#=2GT5>!QHv3>r3YWFUy~LfZI%keVZ<+KXPC=r6V9L5?PIiZr?5G zZYLE`il*T70*aOp!b?A)Ja5OHVEW@)xLy1$!o>`XG#H zeq9Yxht>^BcJTRwPto6W7#|w)LwlN0khW2j#<>e#nq|p%CyZye`7B|sB#P;8@2u?l zTk|6@6O|n&v)Sl;U)`|UQXqQe4q0Z~;uOs8TfI~lQ0e$)hq5_~`HaI;A;NCkz z?tu;VDyH%G$m)cAqx@~}$6jETp3kVITSpy$+!gvSg~-GuXA@5ZM`<5zAGy8i8n&T~ z^tP-vAW{uN8g`Xx+$rYD zngTirwPf9dY@q;;P{+HtvZsK5HUW@rI z2;L31;`_mdzbKH2i%uJWu9Mb`;BGD>eQWYCPi6Q7OT-=8gv-W52{D%He^(q%3D_mh zex;KwdQMG2fQHltchBNn@hTG53P+Z@n;zYzK)4?x{IdxDiecD<28G^wZD7r8wOjN$ zx&R$8@udrwXk3Ma5h?{TQ|+7x5}BR`xWp<@)`f8N$$Q3b^8cO@+qN;9oWfRsJ($sKYTuu-L5ANVIdvP z%;6)>iQ;^CtO86kJ(fqpmD$LsG(84t+@a!`lZ(>5_1cCj1Qk||vM$^`U>8ts+5WBXr+`BIJr%F8 zeL!)X9KCPQX1lHcJPm7}=j*cTk) zgA80Ob=uf_&aBq5^QfcfLJdL3Zms*OU#C_|wPjF}A3STT@>TeYo=J2|2 zpX%Yb>pAX`4fKmIJj(-u{W{i`jSU#60g)GeLR=1&RtQmEN?W3sr5eT{Jc;0mdP$ga zh5pnD8#^eI+L~ECnEvI*T5rnR>_i zdyZc)ylnl*$jGiWGS>fz-vCL?-?I{ENmV>nQc+Ser&p9Rf{0+3h*^M_>6A?>N&bij z=RRcb5*58XKCyC4piTGOz|OZS@r`K+{R|pvpt|`d1XdUW>W+(vIf+7K&o_s&EWG53 ztA+m&WjR*;ARG^NZcH^XKeU6N4btn)g%#F;diLzx36n8rU;Y3a({rF4wwJq_q zucJ-qKxYT7BLAA%-+P$toTOw#4DdHl#_#m<`IFoUx7)tF5M;>TZGBb+G*ji`@B**}ayu60*uD0p<0 z<@6T6hB$d2)^%+G;`f(?>HVb~2@|;G)sbS$+2fiTwC_H=vdi3#D$siq#%^*a^;5|) z0K-ZLHQ}p@O^~M%Z(YEb$?P%(sEZ{l#Hsnf&j2Tq)`x?*_xj!qvR8fQx zj=uAV9Yn>%qgq444bVz$^|xsIk3zR$jIKR5m75ok_0|$@Euf^N-?XAyj$PX+Sod{AM*Yj9KBYN0>76FwcG`BzF zra95mdRHbAOQ;NAI9s{ZK&C9(kx?$r$6L=e{I{b~$Kw`e zs??ou2WgJ$jg|+V-##>la5i6$_@|kizY@?sxas>#z%t1C&arn$q|13uD+$94A@+eb zvll&ebzz_<%JMOn%D{O{K)(ui+F`z3YQ4L8F+dFHEV6|59S&_Y*tFgWEOwSyG8Q-f zLmJQySHcTbvI6a%Zq6D0)r$B8oY=cMJUb?zt?qE~(%T$TY-i?;Pd`V7>Uk8;{~VGx zc*iY4chr!y$K-$7_7Pa~(C$S4ap|9jihgRetWID5E09cEpMQ^tX}xhF_$V-b$pDo4 zThH($j>W&VGxENR@$X;%U8p-K6=HL>AoYvm@XY;7N}H5v$mJV1?#p{jNir-pQIck5 z*IQ?I{uSS+XujXAgr%qX>orvF{1kP5yyOWC5boxRhSYUc@q2tR0NC4$hUzcv;}U*p zADr&Ye*TxB6ACE5ZSCBSU%YfWcE;B(?#)B*OBolJ~IlLs5U- z#-=l823=^4w3?PFiixR7GB6w>m@u!HsY08L%K47Bey_VZ*Oqs?ZOgmsiV4ROX^iJr zfbxk8%t!v>Cga?0afGX(ZV89~*1U4+@?Gt_fSJ_nCFZV`!W0$p{^Z1e*0cUkm5?x! z?XP68BVl)IYn5rfF3$Z(820YcE<1PTHS>zRD#)}>vGM0;zS)Y7zZRzd21~aD&;EVC zdD@1P|K}P2Z}z_Q`7WU`&k?EidW;unbK z{|x?FroW~{8xoJO4zwEqF?h>US-JF0^e^r3e=i;I=D%-BaPxCdh_L77<0lM+SpD{1 z|CgWA5%zZgH;#v>8zg*BvYj3Fi3`*hz6Q#}#^R)ZsY3f7*97e0uS_QlsQ1LK=_atF zLb7@bHa-2sAXMzUiX~IXN;A@nOB%wv6Z%Y z;QiEP@+-CfT-*P+l>hrubbJ5;;CYO}$jWc$8a|5Os$MDDx~cd}8|;7lF5%;a$K1o# zsoIP5cNh8nStRpLzBqBx$*BRfV5XFN?>8S?k#?rN5ZHf^neEf37yZX?fKrv@o_@;bOiE9e&x`yilWD~Q2mLa3S@*!l3ojx1 z1OC^#{K}8tg^qA#k{00V&sva(s^aBNps7eJ*Ul+|X4^HX2>oqa{^r--oO|A(`;j%&K#lOC6=!N=s&D=8zP-cv4*4b(W$V_Bu(D^{wiI`Al5q%1dwg|NaI(eoFW)89%=S zF817To$;zQMkscY4OFb$Oh&X4LcuCMu?d!An0A5cN|=~<5|E$cx4vvNwd5o3>?%RH zSlFV~UPSaZ-ks^~#H=s=+O7XDhtiKP*TH}8TttVre2jx~be4_AT9gRkvzF)7q1$kD zHr2W7CKNdWw~Ro?%N zZ~PDc45O({j7H`chU&HMGVORlDaN=^?Q$#m*Fd-FZj!G?08#0wouJxpo1-zZo48Qj z;rCsXdJ4C`AQj|(>u_qgaq1$QMt-j1vsul#fG8T}_{T5QbfI#X17!?jYUn+&o50oY zcs3O{bkcm=Me)kN^;h<9rLtOXnUeI%6k9M$jQU(m90(MWv2?#Z0w@mZIUT$>yr}B$ zJ^6--Y&ZJ_WSSM2%x;C*Fyev27s)X+=6+kW&!w#E>+|C|9ZIL)2%f3A^1u7||F#1zJFfoup#c-T zQW$3GI0BD+nmf*@!uA`FaXG>CqNGg!cGvNoaf(L%-}y+FPbq;|Iik<(=I`BKp3~!()LN_Z^yUb&q-eeuU62AdiP6j z^xqEk%j!pwbyV!Lz~4H26v3-QGucgl>wPC5g-9GcR1|+Nk@+jqi~z4@R#N@)Tkl)^ z1_-d!jn30h{?2y1a`fuRx37NdeS2yE6lrqJO{R0dv&Qcpz51#E!><^t|Kp==W`by3 z+*42Z`tNLz=SQzrwB|kkJMVi=$zL3RNmNxkqNIQClY?RcJ3BiraPIL_HpWEun@J&D zP1(P(+vYU=*-ocRUIG0>6L9`X)&?;N{>~@*etb*@`v6m?dPUM(HxP3WUKWo-FYCRn zd!BNK^fwMtKj}WQ0FvHpb>oh*OV7PUp%iXlmLjnK7N_}cU+wVf5qbnGH3Bml2N%`T-@BM-!u(FiC6u_>+7>d0 z59sjO#b8gz?_@l2c?b%R4c+Wl?PM_rI)?VX=}$^u{oQo=$B!GV{_$!0PXE)aK%)*X zFS8ynj`_#?Pe^qENS3%1YVX|gro>zk=p4F5 zMtRQh6NWK!;A2@(Zn-?0ICK9?j7)U)QSj)n@0(aSV%^w;)JFQe(h|LPPnrJg3pNG2 zgs;DG2>55nTo#cx+cJunPdLg7%09KjQsOvi_b0i<#O+A>4q7o0lDJ$lU;@i7pDD`| z{AalSRx<6mBC_=;70Of(`SuH6=zsh#my1u>4&CDY8L;2wQlB*N4g1{GBo>{c>H!TC z>xJZGFeVDp<)~T$zunJ#r1T6%+s5Nsn$^VsaO14k*Bg-$||w&XqQMCP+C&X0aCjG!P1(aCD<707ez z$Le#gmIq?MuUi6)Jg%aX0E^UHCJ65b8&H*loxSyG%Kg@uTCIdm3LIPT^Z)-6%76RR z(ptw=`ms?jU+`C1<{W*K(^h8_2bgXAL^W;!GCN3r$Y^8DK$CLseB zMX;*UrS?i^`0KGBS5!ED<4$QhDwp1lMf__?>ICXa$f@RRX@f#V?Bgp(DJv)mlvWB9 z{QV%?Ob5gcenY#&E6xZdP%tDt-}_AZDH$jj=0QZX)vlkJ_Gh5^+pFT^X;3qK!1Olm z!>jIXR_bh;U|@2(Xk-ak9-UXQB!-50QJDek=T=94;gV4 z_-m1#!J3+hA1}QU-cR7?GJQw$x0mfl)}M_K`^VBWr|&%bIDPiAF#LPr@Y%olBqn{5 zBY(3fvFc*D>5AZ#?bb`M8)}@c3Df+|UtM_Q_>ZDLK3d?i!sxRFpUq(Kr2QvJ!peX1 zNzI7A-qQ#}_`drMk(mq`zp3pWM>>(eyAqp2{(92#h431Wcf#2j#z#*=ycUrIU;fwm z*!x#++-cD(e~>1tfy765il1d5yWM6fOZ)5JqQ_sYj*w&Dc<%&%oCf<6?vXf5MdE3g zx=>p#5v-f3c-j-ba88|t&*)ft&87BV-=EU={`!$krFk`a0;RW)QqIXZiOb_jX76wF#C5=({rC44t-I0$?R%w_V{T%xrhlV~8v5#I z!vrUESz*DcWYE;`GSBgxL%dCSd%Q$f<+)fRw|8SeE!pW)#vTbGvK?bY+si}b8z~*) zwHGgxk_{#BLk$}5Z5h;!?JXpMVqg!y;*i8@J>g#QgTN_%wy8+k=!4Lv?4QCK?$V2$ zJgu9P+E1C+0Z}>f7^!!#AqVQ2XMN#c>`qS_*STC)rx7c233&UjZ`Y3zr+;?d4#?-4 z4QO_~3aRFvO}PMI=X@JatZ;0)ci3WcaO)UDPKYSa zs;Hh}TzL3(phxBgCPf#)_#;Oe&%?2Jsq-913NyGD?RFv_V05pj^ZT@8-1)oaqP^L8 zp8B!5CY?}IU>6>*GlYo%^|G@bXoIuE@w<)-f1|nQ-~QLyiayqo$bDG!FxRj7_2Rj3 zN{-LXH?BR-^hmq#;`M1I<yJv(m8}(C(4j6m|zZN~Ikm zT^X@W_k9d#$Q38e)^^YBeZ|cs(uOy^7w@gd?yMX}FLXe~IAKYUhrp@LcIS>rb!VqE z(3@9nq{Wk6dfm1HK4yYQF^BpfS2V0tHdsG(YES#V8Gqa(G_Cs}YN8Rg-LiRDAo>3}c zHPvEzv+jokRa<%LOBbgcScU+;)+j|=&o6G3(|`5UXb$jECIkB@$`t{*c@-h8p%09d|N>Iv5FMYp?Ri1p{=9_YA*}`&DMEdR^8g|5oefd01qs9T8G}B)Jxk)64@5#({5r7&_+B0>X z(WO;e%<`TE0GBs!-pJ7wgTPRseAAnMW3MCOFbWu|MXJAVpl|h}6pUd;97pghhTA0- zIXu;Tp7pD`RBFY#?=-%=PP&dbs1*I`H160?o=%&smHR-_(bH&UpRgg&n>S0f`$%GZ z`9>vvd0Aogl+@w=eTF+eg|&yQa)g$=oaD#qw7_Cjr?;DwXh!T{lzjZ4C~O0;x5|sA zuX-om&}?e?))d>m6L<1|Typ=ohUd(GoqN9L`IOJfx&r= z-A?V{6ydS@f584kDZJWl2`RPZ&?Z%&rYW+Se6+_QEl+P(F7q`*=i<1{cdy_Hf#v|u&^7=5UnE6MWUnNP9ySl+b7f~PZ zjQ7$0xoX7bn>$XQPH>p%a%eU6UTeEcIx&)L->(83D@@k?2DlZW$L(>Fm`e%`-;eMH zMNNIjXck58&iOGM|AmOoU;Ijm@3`om!xnqx_ujV9=F`OQME8M;^b}5Wb+KEcgrAZl z7xJs4k)A$*1Wx*Jo34}0eyU1J<#p*2AADS7OBK}%^0y|AbIGq+0~({cftR%J=(#8` zd0-db(z_LSb@1W*MhhE5VzQ`tRSgitQXF{H4M0 z1)vRy0&28NSNgX)fd)CqW?04=A)#M=&XB-bO&0Vmy@HAr9Y8)ZR;!fHOj{3kP5mo66i#rczE2EV&8IzFCLb1 z^=g)K-ea=4psoAO$msho9Uyv@BJy9jm9|e#(W-5Tgx{1xj&JC3^tA^UJT}*nO&oK! z5BS`)hhulz5owf2xKtLpG1~792=J&MxcY-;^3!}wTHK*_9o`3qLR(!O#RSV`wg+ql1N@4;NE0jU^qu&3h*poTDa zz;(1Ddp9DsC3BX777$4!@mdyL$u<;Gfdf6uW3dJrjc&q|l)lvoK~#WfI=^1ZWuQ~! z;}6K*H7Li3_LRf-z|iixn)6lyi^atEYwLP+0{UYJPmGzah3FBFRjV?$bj%gE#!v(T zpw&slN6~>K3(ZAH*T&&+HSO2y`Nd(g9IuUzlO5j)+mV0xnP%wpyR_8w#~IhJXk_Xf z%Tg{5@Y9kh2^lK!8#*QP;Pk~muXEPAE6Q$mGB^dtx2#)4tp*(j(*r)Wk5ytM`G!9? zO(uRHg+Pk_WY8U}g9q2}5+Qs2l+|`)A6ihAlE(B}g_n0$q|C@XFu?<8;~Fh?wZvsx z%v?*;>H{?SVi_Ii-7^Y!^-=ClUX>#c3wJ;p&b^Wtrp(w~BNV>j(iy-~!PvRFz5q=A z4deF1Rutj=8dQ0>GzYqPSn9JgFO=+B<%m%gQk>;r^!zltgM|^c`F? z)zu`OLN)_X4VoO`73E}u-dzvZg^Q(AqZ?+#D(X&AYI670KJC_6sRk?+KgzNP#|s}R z(Ed)8f|XA+tQ4W4mk>4n+w1+4&S9prkIg?nmywa10oG`RUKJkYf(z(J&!naHr%bzW z(v_Ij)^XSMH=K~-$j+oHvB6?K#wi^scdGM=Mpk66rS{B<_74b@un&?=NT^V3L9v?g z3%!Exb-YBy%l)=fhmySq(vu0(l3W&Oy?AR6ci$1zBuYn!0Du52)WTu#CjmaY!;8E5 ziq8@=7!r4l8{QevoKFnh7CD{RS^3pHbAy@iD~--6-4lg+waP^`nd#2ojo=iDZ3AnG z^Ol`8x1DmgS10;KJ{NNhw&t1h#nSx%5?j3@{|rW}TR@FsFTE*CDaB zqhUec$1c9IamnUbLN1QZuDPzW?)v!Dk!YkUE^r29k1BC53v1U~A^CyGK^600Spqj8 z7}Du-bj4XtIl}Z{07U)PWqFmF)*4NIn#HRmf~jPJ`$>gTRMSgsD0P22UE*#qBL($3 z2jE}OZ)=qWI4f}7%)>ZBca{h5C%nHUA=d=L7Uu<@Wo*I2*?VHQu>hEAIi8oFOg?@>${4g{buj3eiw<2LMNP zQzx^cZd~4F5TMSh4$QTv3GxDzpvB=FJ$l=>!!S7D*bfbK1V`IS$h z=~0tJX?FGG1(pT9f$%k(Vwew_2zk+;u8q_;&!$VH87Q+}i-(aH9pCR!NNN85Br3p; z6W@OLwSiZCNS9)za#@7J`o~hd36%8r(6>8JkWh#&f^mx;oSQ3gTb*JYhH>MEOxO?E zsu$b7f2*fX0il3>U1b%GEA-xa=f^+zz-l`tNL^hZLuB8TkUGMv4}JBQsnAFJ2J(C7 zm)F*%@6dxG4jXhXg+<-;+}qcR&s+;QGzfD6sI%dX(Q2txwD?rrA4pDg1D@(Qqe)l@ z)q#%0*r+9$?>pZH&Igy#)nem5Zh@HTOB}k6P~-2zDDSWxi-kOy;#+6>-_l+ZH-jup z+8)y6^CTTFXibTKiP<#{9x7F((MJ!)-1LmxNmq_Y?H}j!rk)nhz}mAj|F=5lKf*w! zU=q03Zun`ru!Vb~F6-=u)hv59y**W9i-herX@SY^Zj-?Nj6E3Zk&JF83}H{ou}v*Xkw390CIJG#uAj)8JxRYe@4< z*IcCs$M2^I~N2VW_tknPt@jych-{u>$}9H(8J7|&pCmsMLS zI(%tw!3ID;0B{XV2ymmNzQDhIQc?dofOGFGqcaUhGZvjAAm1vD(Pj-NT>rrW=w|5? zHL9ShAOlEzkX>WD50)-+TNpdkh)5Vp+m2W5^w#KIljnwKy2Z9B|(N@AmCVBPj~?Q^V6k30(}Dvs@h&&LyjOvu^~6` zGaonS(IUrukW2`rdF5RyaVW!h*<$jVjn0U>&X>V=^w6|b>8_IDPL&;$M>U;G-Ad=^ zd}{$WJu(rQ(lauVM66nZeVvkFTb;*KUy9@^`p}-7BII7TE=5~ z^!jbiaEYAs9ii`eU>-uA6w_Ylq)`dPpV%?*Rr@kDo1Q%l_{U3<7%^R7n> zUp<|&aZ!VU!sqL;>XPwXJfWT~X;m9_-gik&>!HAYZq^dXB3?iWvG>J|#?kX3ax|2JdQLtgD%({h|!+J5c%p!x)|NW7FSVg-2>zWZ}YW- z1?#J&>#fEeQaO8*s>{G#T{I)Is_)PdiNzT0Yy-)=KyTV?#7m?_V|QA{92b}0qCLB1 zE~^~%TOw}KODx=i)3-X%-9ilg0!s@6M)lqGaSnBU`}L=zV}yr7)ImiC?^;hNR1K;p zZl}NDbbUEacU>6yNEAuALZk9_jTFo_-oGoCXILG>aK>rby?JT)wpfVP(S(YH=BKgQ zapUBPLHhE-N5N^J|Ngcg#yI|Hc*YJ>wawt--GUm6EO{|u<{K!G#J4oiN}T_Crhm-m z@Hk`mFhDX9T{k9%X*S=^ewT_>MF?XYQ=%Pr>1l7$b)}BCLSz>iOAnh^V)fFd zAIT)`K&zlKW3AfHq53Ebq&bM1u)BF_^OEos)xnL8+tH=K%@=u1%BzlrU%2+3af#Jn z0?nHZdLBBWZ?Ygf0M%7^sR?cel@T9mlZ8U65B(;z$rjImITInPB^Az&gResK zbieuNsN;uCq>RVeK(*&s9&QTmCcklyI>n{fX@LcK7(RwLl-MmjFiJ|$F3a3m)UPx${AVAUOl`r<9vo)KOS2%-^G-4faxNJMcNWUzLtYiRhGg@a z;IVw|_!>Qco(9+k0B*&7IY!uJ-Fa4?YZAYy@*(!p^>*JD#t%%0T)Bhsz)Y?*Skq!GmzXRfWT2U5_s+@Ixm zA*rzL2F&V8qi*V>h6ht+K^*1zdujz5g+|rY0(K+1>$$E4YXzOz{gnhY8(UJ;1(&zc zu!hx}N2@>j2soMOIuJkLeA+>0jJS~S%+1^s@q8S?;&O=G3cVh2Q}`R z-1N0bDTFVIbIFCU%e<+Zk==<`V1Ym2M$c#{DJ#bq#~gA(PmJ*+t&u6CjLZEc-eKzs z2cKWQov?!!({pRwJxXIv(uxYyTk%OZH0c<5#=qeP$-8Pem>)2fTHjz1rxJHte%o61iM zF!kRxcItH=e*|CX^q3b|oWD$L`vXr;vb#Rabz}!>FXw!c(B(uFN#ik2blr_lPti$x zOMn>`40No$oCe+Pa7cmnC{IM(1}!nE{$LDmnpJN9dTE1vgkKAZc$lgZ-Rz z#Q`0O@jB>85Q^ABdS~!RE=iW>qfg}6M6b8;p!o}Jnp^o8m#g%C8h8Ht&GqpcXDTG8 z9A9RI8}wHEq89g-+t^|tSgik~Dj!$Iw}ll2K3=Ffv_KV>^Yhx0Cyiw88!NMGvzeVQ zj0y`%+8OhLf6#Q}@RqyvY^zZ*@5&to^>ME$4b3oePS?gSwRfDFA=|?&_L5akmMYZ` z6l2tNm}o?r(GCF^*2>ZvjRrs9>vn&SUfX>OMvJg_I;F+~;)zlN`NSQ@OBYQb!~APg z^?@22#~dsnfv%=aq5N{PYHZAjm!#|?El-W`Bc}@KqU6c}m{^e=`jZRK;xKl3iK5E) zfsJL)B){2iGg}y;8hQKcxf@69{liK0N~V0s`{X-}eH9^%qj; zmq?B%m(VRvgNbR5_pzwaY7q$o$$7uQl?M7_TajC3?2e-N<25vju z>p&-k4oX*%V^qECCQbCpmb_7x>O{IQV+7GIUS!s_>jJVw+@pCb8Rql7gnkapi`;#M zd1gtGgJJGo{%TxJh^OQ?B7k(pJlJJ?1pqzSs2L?^T&{6&v_+{)Cas%4bL3L9c|Jju zZS+P-xsaqTTn5;^Sn*vCIO>AqGFplYuAd#kXS{~c3CYNakW;O{X)IwiG#hao~*Si z@dS+Lf+_dap?j3{lA8>Lqfdd5Vo}++riZdU+itkDzvz8&+j>gyIz69DjjtL#r&hDA z1;tVVv@6C6%=dyV3a?L<8I9gQd(S(+%O={(6|+z$Xz083+I^0WOBZQ3vi(APsVYu_ zWtqi2+Win0WREL=%o`bVcMp`BYvy_0r}j1~n6#nuv9sqe9LVpMNLk52_0|Ei-GrA_ zbe;`FaXch5G%-Iu6`wE{a7rk$t!A5(bjyHqe8j zqhZWK8m-Y>D>o?;dcH;m#f^0^lN3?R%I+SjgGRL(_)tSt}wllXU<47NX?&M-gNFE)_4 z0<}Wjdwp@OPN*<-`K0j!NP{8c=4QnJQq*fbK@Sl^ce_AiM;QbcBwUG$aFI{1P6%|U zIeKj^+|q9BI-fjlQ&Ofr2}8!-R>4Iuz;}aa7_QxqJ+Ras!dPKAhWgcrAaC+Q)*IO! zSdTK^6$evRZS@5N*WTLXE9Gw; z)|?Rz%cW>&$!@vTs0P2S{m2simlja2w?Ds@NAMKj@;!tEiIrFqqppMdNFZ*A_E2K|Lz3Sgs*$!mN#y$w08*U#OCM=Y;Et+lZ=m= z0x*Cx=H)K*iASeGifDffaj~!s->XvV1%EfQ*#jZsKO#RjGE<~z zamr`}rJDNt{Oy%4QgV z2JCu;*LWx_>Av}P;arR^_rjOED5zyuwh@P7a$o>YqhvJTGZ7^g5{l8~P2>8wbr-3_ zvFHpIpCw<Gw|bTUmIBd zJd&-F%neAEbrKE-41%_PiO%c87I)_OBLRVc&F1NJl>JEC zwYolk!8?sE?)B>(U*k%lgZbFW%uzmxaYLsgcyImVHU48VIKLj#pcmxj#9I%(_DkK9 zOX_#1lHq@&n|}Ai;j&*F7pHCW5;{Wi$XswcDTuvj() z>hsi-a4U$!B@2j@U=ABQcZWtdRk|n9TBpnfWpdEpAApUqXonj~ ztsD?>Xct&2q@w&&$@^%o!#APKRl{3Q0ennZ{wKHK6&9q${`jXxx314hJR_`79K6lC z(F!qd=G^QUfRPK{i3Rhon`~!P2-^T<8M8lif@LgR4MleUY`G z9=poa1dL0kP}-C@p)Jo;1X=yla#xj-?so|9!_pJ+2?zQPwR+sut7j@xXZB|FgpN@dW2>yXC7^gzX-Z zfWb+RsG5TT+m=`#eA15>E~WYYP^;44DcM1nn1BQNct-NC7n zAf!?9Zsb$_BoU2zEXYRnqHiU%`IF*H{e~d-cf>b?_gFzqoTcUS3C%S-KD+`K_0ip` z9CMOMW8n6nrmDWDEV`_GJ{94?D$B^X4mECLa={gx6?uSB7G9RKugxIM*lIlW{DXVq3Bx z>!@R#auGs?io2~Uy2nbOt^2J(!?7SosvBq7l}wy#Vu|tmQdvN@0&o!rZ`7E>o(6S% zthtmZIQS@=(V)L|1Yc;?XbDLB;uF3R4TtH0dMwt~0R&LVHAXa)_twD+T!O)=dQ>C2 zVOXVAWtXxcqDl1ttsv zO^<)Y?)>ouXX-T84f%GIth+CDq<^qgf5Z1WS)puJ+Qh50fd=TX`aTsodwq=uu4wisND|57!!@RlFQuMR^ zU@G_Ov2+({T1&!&yxyNEdr)Gj^y2=X%tvr?MQ$fJqR8MHz&y^bevQ1(9b;5mE!H;O zUS>0@r(Jx|#^AMigQIP(#dM7_?5OCny`o}K5O9wZ%vmZf$Cq2}Tok{G8+Q-WJ86S| z25Ipa8+ySSsB59XD2(CksXr+DSm-_xm8WTZr(q3yR+X{k08TJpXcrPE9%Ur@4pc@A z`dp7~9_$es?@Nr5>09+ zXS+NTy6&jwl%HDip1n)0c8J>$9WL`ksb)%v&CJxG#XhX@&+QjNYR76aP3xrgQ|R$N zus(8HEKFxA3@rb9E5*ml$|W84ZzTdfF@jJ*eJ)XOQUOY5Trf*v4H7-mT47ARQFK2z zf`wq)cp<5c;2ivNLm2qDBauPmBqP8<@6gC z)TY=F$%4KOAnsaG2?-}9!TKam9sZX>;y;f##{jAt$t^moZ(61t;_$|3$Q*K*1qy)3 zmaGCoJ;@T)S!zgqv*9qQq46Ft*rfBv_@5Ex+n23EJbj&%;_v1e_Y-oa2iH_09vQQH z7TwzG_7(Zy5Q}m59VY4BeXp5hp44((xYkKC{gvSq7&Jy?WAvY^YFvJ0+voEVLk`+PLWE2AcoIn?EAybQ3phGZUm1PK4oL_J{UL48$5dD zbfTIH!0I_}jJR%BCf_kFJ$D0obqwS91zQ)xO_Vv$1HIDbdmvw?VOKIxW@TELF@QX# zV?#I4#3-;+hQ>l_p2YYnlu^E%b)XUyrhTLT^!>b!7lWUT<=*N1V*9Md` zh@FKxm}mZI{SUf$9LtOd{sm*AuQ|S8b0Gm`8{#NAN`F)LI>=`$nuKh(pO1MvFcXG* zVJZpaaRSXu_HFk;Fs9Pi^+lTJcH9aPzd{CJo{HXge%zLex`6(-v*L1iK)Tt)!ZnwZ z;v2)WfLPlf8$e5^B3?-AgE{5|9?LNwXV=OOFYJ=-3asG7&dLGk@TJwJI08+ChK6>^ zuU?XWkZgA9J2Xdy4Y~vNwvm?7#GyxWbe3>S2Fhkot#ai(cW8+O;=*FXD2feRlnLS_8SRp4*}3 z8Lu4Z^fzqGnnd8+3J(q6y?LR%hU=7i^uhz-*xm_AKTmfl>4sp zG=E`ETCHLkP;s@;-bX}MO!x{JKF5#P6eu;RDZq(Mn6Ftux8=%T?Vh$B{rvfEwNnmx z*kacq=XeSIJ=*9);>If%Vm6ytJ@$wOzGY$8UbKph)-mJ;ME%|EXQ)$0!0qJMvm(M0 z(^)q*>nWVYE4aFG-LRC-sGf`_?=M){+^OyQz~IaY(jAG>r<5^XTt1u=jr*DCjWu>j(}flrP2W^+XAg69oki55JWQD6&*h?#k=-H%apkH_nr zE+eUXUr3&Va!v$mn@9h>L5$upY+RhK=l2MTUZVY!@4HXL%2PLm;Y;1Do>K>GU17fJ zG?zAgswQFExJdi1?XuSmKQ6p-+Rsw|GuApft4ydYJ@9!Pj4v%D%OJvsBzsd)T}YZ5Pn0_Gt{f5@4DX1UG_YrRZXa6h*gNU#F=&zO!7{I+3%K`iK4Fr0U+a$e*< z5>+wZ?LP*Fv?y#2e?{F1(^l@7*vt1q8E=}mzIk7b104`%G0CVRXjRck zka+IU4%jU1C3e@vvcGcMMYhG;>77uMnsg|+3Ei$;d_ZsTK&FfmQRlO_dLUqP5DD0^ z-d~nv)hKdEvC?I4mptV%NTX3c0&zq8(nxH6HQn_|-Dr99<{aTtT9Hd3kw}A7H4H-b*K2=u;$5*7Dqe3~VSzCsAl-sYF~5A!h2nP?c(TYX%Wb zphm;F(JCPbdHem7qHlp6&8oEgF`(paem7MYAqemE@ovrxoyKxXijwli#ow8A^_<#Ja6jZgB-L) zM?&U2Mxl6A@ATt)4gOGO{JN-n>yD6tjZWt-?cf8GS_(#wDp2IMS=xAaPkne=qASRX zictkrKn`y~YQto7Rhc23xG)*g_|TON&*!R2=+3{tjEVD_}_wCSN!4vK6DXu z814(0Ejuupf%&ZzcM(dS4!abkUOUDIth-JimW{bJGK`z7{YIx_U?D-;@rly7s)BC| zEO`p=i0PRSp2$)AVk>ycdW;oMmkRynfGIhp6_~4{?z{geUgVm0e#*Ne5eioS-HbZ6 z@0+ErwvMH=PGD6UG5o!|oJHM=^>K|Vmm8R9Hcadn^%yV}b4Vn{_nv{rBYBZ> zTLuD3$@r{wRP)^N6rHt{AVI|iCJ`E*mDP||jZs*`Vq=G@AL z0IkGOa~%g>9Hd)qz7~|{nl}>FOX9(~yRryh(PmCs>82bPaHwoK*y4kdc~nT)6?FVG?x(Ns=BCjo@ut zPEQZGr)frmtAYw8Gj^3Wf6WTvaeTq&d)-k%CugG&)fqF_(6YH9btnwyn}{v;=m5>Wz|>(<3<<6JCL42-zt8I3MhE!*6qX8=u+40zm*7%iZwtAj(EmJcADvEa80 zub51>qv5!PRPGi)zU$D{_>D8Dw#%O7hn))d9i9h~GmkY!=UR4kPm)|`u z+VqOy((Mlk_HsJG=+;8=ivk~z$BHvE&3e8LesZ7}@fSwrkvBmrtR9^wRWtV9QT6P{ zQ}!(bj{?8tSI1O{T1uX6J~N|mp~p0t+ijF(#Fg~^1RW&w7Gu>`g=0R3Jm1=8_lVov zk-s~f*b%f2L`nXP2N1| z;cJhIdPz^Ew<9@4!#kXJY!LJlgV(Z;4KE`7oc&4264~T_XI+KOF^$vA^3!tot4_N~FwKNII4i;_iX zun!^aaMVja>Kf2a#EKyc&v9!tZ$0+YzQO`ZRf}1FxSLF@Zuj76TR?lCZQZ8ZwF*h# zF}9T7mhi@jvicHlG7H&f604PRZJImztUDua{O>9bh%A`szHdi`W!B5#MiIz;%VI@< z_^H_)u&w--oiINBFagM^In{DbUkuNE!IHtuGE@uUjJ_YvE))~_{X^WzE?F*xuJ3LM z;$Ax~okIYl?7pfVQI~Q}gs|K7;K>M2B+(p~f*P!~lD3=4yu&Kj!N3fWlc&BYjy>_Q zsshaSvsqmZTbDfa0exbi%n#XKniv&PfpAR~)VH<2|Lf9)x$$9#3la>xtmeZjLu*woZ$ER0v+3~^v6Gz|n`IW}t^8-|;vgLV&Yk>xU(D+=M)!wVLx}~jl!&|!U$-oL-pKnS~ z*OkjyW?9RIgi7ss6fMDm#4?v9iw+X=AS>EOv#8gUKd-xrrbI?w0X0_Ns>|pijNL}v zqFSCQgcueQg5k~qdCP{|(g>3f3Y0W?K75eEjh1^u?LT}ICB#7u^&9F&vcXNBl2AK^ zhfVpVZ6bGgUk5pHs9#!vvZYVn*UVO_;F3>p9w>1HHKt5xe?cOBxEBt~8qK9sTH&fA zcFE?MPlvHiH~Q(>%B6GW~b($wycrA(5eF7s5o%V#&#U@$cVL#ad0d()FG|8%h`* z`H&#G0#s?6gvOid<@gnfjjj_PYRPQ`OfKe=Q8ETGZWz($Dz7_t?Sg?|_+bV?OGl}^ z2h-v(kue5$KkhNR^^#Biswvh?1CEED%lG^P;{4rWhJio-M({~b$-?|m0*)no7Yuua zq>(a@Zj(`|aa_CBa#Z|b;4>1EYk0eG_lT3+4{I>!{&p#GU}db_reYi6TXH1hbskQ( ziY;0*|MWCZ?V1jX7ZlY(39A@SUaxHrlv1}<85kKl)n70G7`Q~zy5s4SQUK^4F)`rf zj_IO<2aVn8Uep8dWv|3EIfWJ-W8Yaxv#PyBP$MP<$eVC0{uv#UPGY0xY(dF{S$iQJUedV|7A;ER&v8jX$6Y`Viy1>6sWV&-%2 zi|%j8zjCE*u)>o()1I5C@hsR${H)w^=33?}h`)YHf(c;=Fd$tgE0leFYAnm`-E)0G z2)8*19ChpC-nLDAU0_ZKFZaTv&40UifdQ<)osUN+Uje??&dIhNN)6zILTB7XiQK( z+)!7E=%|h|`}P)I`Un1OkE1I1|3*owb0Cn-wgiszAS(g z7~L>$OSYyZZiW<_50%g-c8syES#&_XWydp`PDUN|NbC0S`V8V8Pf@DToI0(1d+o!M#PD@R#(-4x^KXImzB1Bo5bC2PM)zyO8#Z4iph}B#CB4 zkY}D*!2G%F*RW4#%VuXmlh68XUb_)b2iiB(7X_8E8d~(M@eb))H86CLhbHbxeKsRMaXZShv_9XUnAHGjvSBa6oan}q z;92aaO+VPhXNMoYv{ZaTB?o8MrYGa+Xax9WhzG1E6N zIB|Z~+ycm9H#>x=lG;3r7Wtfp&0~ez^pbj}_V7Cwk(@ErYdqAE3th45!0Mm@jI`4y z=uZSuvn;K* zZf!X)cE-}*d%a_zFka=FDbt`J4!?PM>bzs{oPgzIp0ijN71<;Lt%cte*{pTin~!Zp zb$K@It;3(a?aG^dDoQc%iylf(`JY_n@jIlEI`$@Rotl7P$3u)-x}!bC%gvoa7e`b4 z*xoI)48`r56Jcrm&U!tJj$)d+0_N%V|tQC7DkA*Tk#@W z)Qe8QIm$L@Qxd{U^QIS`#U26kr@qE~%POJ9@JZ{~EpWhu6k$ZPA5pe&peOt4` z<@Ac+T5ntn8?BTDi*#U8G*6zHj*b*IbEXtNN>flpsKDlDuS^{|5SW47YvP5xM;GX< zUIyo82`-<T{=Vjq|(HrEyNVlkb@Yh;lB8{Zlpv!R~`SUKDFr1`h9)I7b#7GWU zo~66DjxwiSG@>|lMbH1^yHk!7ISmpAc{wo=pl_}uD=YgUfobar7;5@~Bcnf0Uy`Wx zp@G3YfXY&S>8PXzR85A04kMt$Z@k;>SEns31%w}&57GiopKi^Yy*p<%eBzgqg3FGt zesT#$sBd3xTJm)qI%@zJ0N?ihFax7(Q?U%NL|-2xah+%wS?QU?_3@lwts;XP>W0o1 zK<>$^Tc8?y{fS0$m73c5e_ap%{aCyEqwGC`F_HEVJ|%n}fG5J9d2NR=0Mp^I_FZP^ zd}XTDcB8PY{i$wFEC8`bflSccLv+wcUv*i?L$_?k#mELt+zkdkDJe6EhqEeeUjY|x zOjGPJT|kF#{I6rKq&t5il=&Sm&kMx8#cysQ;Y%em`shcP^lpp(#pCcFPx|*{C{6c|M_42 zSE+s$!`qH+Y0fz+?>-tPC5fkd9zuuJ7{>?yjj#K!&v@zy_(YPuEg_m_4VR2LLjIid zlO9gL5@sy#C`yqla{Sk}**|~WM86}@iV(57Jdf7_l3yGve>|P%!R*+DJg6i)o?_njzoCT+z8nV033x)tY70L((LUO+Zhpu`Ba<@lY4XU zS5KszIcJbpsj*`8^uwgrFkDhW_ZM!lz!`UWwfmp8jz29qr0(;vNcY&WW7GNN<>gf% z&i7L;kbaA~nG?4X(d%EtmM)e|PQ^l!QNcmaJoxu~3hh?m%O{ zd1*e6{aBiqaQ$2DJwO;jZM4U@aVE+F{mL#|%Q-l3)yK-XNZcSNE;@#j%v^PY~5&a?zuDpXzl zRfSVzpFTpvX#K|$?ticN>epV%;)mf`c4FEwJdB_7WCK0dv9DgfyvvxSRYK|OGfLtI zY}^&+k^yv3McpnC{k^4I{&^u}X?-7uehEMWo@W!xWQzREQvt=K1^>d;UgCFS;)WjW zuhJeX)5=~X4(M&(Yg1OxpV}t5V!C!03(&=VLI;6a&KTU(+s~gr3*LxcPDRlj>`jsw z5Z=RjDZSa@2r^x_J5R~w@Hf6+c#)6D&mPE=7gKZGa*O6k>=nUJXzasx=E$Nx-T__E zJ(@j0)aStVez-v5au6}ma(noc=pfDXH1%VB`HHjsKi$xG->a9e_ZD6hILq`j8Lsop z%(v-_H}pKD;!JiWs}?Eh*II1Zhqc*?w=FCz&5s6`Y`-mYy82R zjERXC{Yv1#YgpJVv}5)zAg?>6wIzAaE+}gM^Iac!1yubf&RT94XU)rj5`E!U@8J0@ zUft{`IfFP`^LvNAurrh&l+RDi%xKb$;RN{|89>SCE_hFGY7_<)*>fTmLY^ z{c+*{qFD$2(jVvTBBe#$n1p``&HNWqdi9t7xMG*goL425{Qtsr{{J8Le|wMrFF#)T z|MX$=6ZRu?b#*<@9{II*t)Jt`)2Cmzp+$ueV6Oo<>lcup$64zMM8y7!1@Ld{d+bh# zH9%Vn{Xkpy1@K}cl7Ag-eRm0mAPNd=K_NqoOhl0-Tosw#@my_r!~NFFjExV~#W` z#DVVTZ*$SOr1e>}DavvEWVVqI09&8<6Kwr+Qjh=oo-~Ag8U1ea;e4p4aeju@&M*@wmhJrn>cJ# z6AKp?B}Ff~lr-cENV{B}|CP%XegZJN!V;xTw@p!2h0NFaxZniiKzw5RG<|d)! z1TcA!*qf!x0!`}h0)j%#jf!$-;*AoD(!7$#vtL--*VI1grKn^U-25Kt14^`^Y^flb zR4!onDA@B}=fS{!AVl2F`an7seqQmGPOh1E{8ZsF4zBFxyFaJ>{EsTpUt5XcepbX2 z=C^4Ag$Cl4S7TGab=CbqIs@f%R~$JMaJ0Sd-Fwe$p;wE0;wp1fYw*ZV&Gh`&9qS<| zA$7Q=#-^5AaXSmFXwpW)$(@~ECH8F^(;)Y#m65IV^GYo839?(sZ%1Fpb^`5VaA z0)|ll#5-@*DFMoaV@oY+|FCqq`^}Hs=D+Q}%lBRu)iy{5GaBx}z3TK~Z zc3fR@wn}%+*4Px1kKN-(9%;<@LAI_HU~m3>vuE*Q!cgMqKYv{k*ntzkp_awk-{$7`e+FEk^k5Ftn{XK(+}MwS}NuG+3_IDk;`yji^Z+AgB4m>ZQa zHgrU9Z&z()BYeM(4RGvpUe zd-3PwAqHN6G_1B5K)d|-`U531=aGgx;}t{M64htUswa<8tpCY#Y<%|ToySY@8`s?D z+FvC4k}DJs#)0AiTV64~3!{BF+pOYMF*hXvpgx+H9rBxYK|dY@I^%NOGxqkw3PMA% zK%TUC?fXQL6G7Y#2(5%PV&1DIkF!0PsNU)Q`f&TIay%G_iEpxwCeW67+L;vS&SSA7 zUQlH&&<`WeiRb-vT`j~~;!k(j*~fbnR?CF^>|Ux`WNg3}JZztyJf|yt{n=P_Oy9{E zV9gi!G`k4UlmjZi73B6gdq1*Y5q}YXWj&XrxWiULJ8q`;0WVhI^09A41z~8dZDNdo zry+0K2CGMZT!W%K7KjlYhI3zh@Mu%8``Tf(?~IU;6Rp4h&XZH9S%`BV)9LHn#_WJv zFjTQL{`ih{iuJIkn~OvD9YbonX?LA%TfrjnlhS?sON_f-<#GJ21BgIZIe?P)= zsvi_8q)6r<)Q=N0Eo}nADjNX6)XDX|2u4HS&|_7Dx8DiJyz&^M?WHFiX-#9gtj*6P zy7*1OZd9^v__HtQTZCg-_6Z8VOiUEnh>gcZ2>7i0l<`f-(CNQ!U&NlTJY~X*b`OFm zMh?FEp<_&fxm6C@IW0zsXQ;4D2BnzOB}=9?(GQW5)5%5hw}Sd#3S6b`KWuMP&gS%~ zZC`d58UNWmeB$XvaJT5+-o}r;)}Bx_NP(2SM&@Tvoxfo%gb2fPp6qsC`|=_wxj8`z zo|_!d^tEmAsp4zh(Y~Q6|MRK9{a_doGJ+-uc4=@X_OuV3)3sR88Bsn_DJ{;&EIH##GxhR=0$r7RxE1pMlDG~w zOm&rD^^gWqb{G5VOJaYtWXI}bDK_%xE~J4Zh=Epcfd zz%4>mHm5z$`Is1=g23*#rCkzN{KnC1*9izpS5xnR$(6C(;wFs)R2(7C69LdHeNsV_ z9d{P#-!eU(tw4q*FV-nhjs7=D!vENo`QQGz?ARpvYd$Rk=w&h=?)+tOh_WX;1M0o6 zwDNc|epk-G(Gc~- zPyQUZ=S&>pulCR*6bZ&arqZ=Y`9PS-mEU6rT^?C3^p*V`>R%14zWtR2Dl+t4H&?js zM=(nbn+VPfco#~D3R^rs)G(L1@Or%N zuqvzFN2!V3@oiQf(?}Hr9Q6VW7gbQRGNOP!<z2bjJU_B-I)B6{P2QZ@b&E9s5`EBv9UWtIw(n`fk8cOH82su+#5}l@S zMKEypmBjPLQm+0!L>_CsV@26C_IOy_mSo-_DX%;;erj=bu$ZSjPAo~i)zncQ3>~by zj^2aA2be3SyELX9Uy&_uU$$-a(;iNur;}g)wU5%4ZEVtk67KyTEBu$|r+~)t=x_wN zCXCSr5HpHA8=wFNkak?mL=Oz0i#%rX7v)b3BzNl}8)t_qG)j%hus<$#D&`BURTqFO}%QHtI7j`XXm-t6Z9lnM}ClGVUu^ycc! z>z!>=iGv3hJT@o@4P3y%wjj^u=LVNlj_nnr|RcW9@;~#1))oiX?GZ=W6&5 zs3Mr+TD5x|lt;H#w!X=l@vm)Vp zfTl?%`L|zP4a_q~#7Vmjb-+zL{M|QtP2EbXC6d{oN&Urk`MD<8>p`0>+Fl9G3U{)gWtd0jE{23 z%#Bssw5ca0;BNnryk57vBq^2h)BNOxoI#G2?5Uu~@gVuSLPD8uvVM-kj%_ zkh8e0=bEi&wsmxhJxO;g^YN`zQ|C? z>;(wPhM$XjPE)tywo2OZ2qv(fH@^375W4wx_P#*JgIv(rp70+VZAcdFxZCvkmUZrk zl?p=J4!wvlQ1>bWxXMer!g!x}DOHEA<|5zDV}Q?#R;P@|TTW4yBqm{C|8QD^UiHn= zN5NvfW(I1`GbB5HS^|&ijT|Z2hVk`2mae9lcngz$SotTwy*5Qe5p(&!x%$MLk5;~7 z2NNzT8}n=l%A5PzMTj)r*3|iBO0A`V(HcsQ6yt_7e(Rb-DPJyX(pmqet^*LCT}162 z`mTcoTcY$JRWHAtl$Yc2SokH`vKovQz;{QPk?dZBjK9U|ypr`JCxE^g%vz>ImapdSOc`&(9IL)JI8K9|!pHp+0FM5K5 zNjc?i4TXV)FXmK!l@Qa30P_AXUI9Q*`^=zCCjH`!lnX$f?s8RaJ5#4TC-Oh4cu$EI zb=YL>gZMZI1FTP6u?sr&1rTPxOpvmO??LW(L}j+e8|=mHg!pG-^;rm5D5F;3`q^`L zzMZkF^1q%|Ou^eWNmBhyExFz0SO#(ov5eF#|5eMt6|MDq+lFoPgRaPPv?0?$V^*1^ z-Qo4dqt?l%Is4@Hv~e_4ooS8}^Xa8%R+IgkJg8f{18qOB)F~LZc-%vgBi&KnC5M`3frf`Bze9SXCn^hUF^UJAuu7ob6xJV03|8KWAp)wCh?&WZ5B zin1Hg9i^(JS9Sjm<-?i+x!f*sxu-Ka>R4xdmY>(0HTL038j-m6G+YM*6QdoO_=!rf zhtb{EDSeOevp06`x##2dW!O+*3LYmUo^y*RV|=`$R;W!S9*+G*yR-X$0;-xHJ_?IB z{DQ6$zuYErimE^alJJZ^OTO4!v^zolqZ zdF;Kq%coWst`{qgp>Hqf9OL4U@Go=wUEbB~gOu`RH)Rs9TfT;ztcvBHbGOd~u89&q zDRu2G6cdNtf?-yA_odUdARcad7gE=H!QZb{rYGaCNf`Bt-GMr`jqYv0s%FUjBb%xj zY2aS@7e27X>ONrZ=%m-e+lqp(hrQ*2cx!VsE=Nl>_aH4LIJX~^J+KyT0*uB=%=F8T z3_|-#0I{kGj8CoPIs#~Qr+V^iR*i@Ep)!Zti)efkJe-Q{_D_sGF^!|*(9hO>7d3Je zxfVXK^F3PbC1kFWsC(HD$F4`10r)o^AI3YazAU!d35spS+Qs61%SK9Ee&PTpKrr`t z!QTYhEz2OzZ6fN59QlathoQP64xg=7oLJ_?-lfHjU%9 zru3lRIVb$kmu>RR25EqP+~p#LBa&2zISBv@u;N9JneK?^NK{!zW1Icw01u8!`Ir}N zX`(Iu&WA4p)Rp>^Scxn^J9fJ51L(%Ea&zSTjY^xWZ@ZK0(@m?Ktb`M;^$u7Dl)?7S zG?CHwcn_gr;q~=7gG*{hEsv`mzmprxv}$oX_aVQ4TIQicA0OU~vE6f7@%PYo4|)7= z9IX#MP#^k?|JicfMq9!dtB+9e*H?eh@X?SNsp8jbYe~h~ZIO~aDTgMlB!oV1R0+69 zh>2+}pM644z7#Ys#p}T1A9OhszY*GzqmRt=TiN$v7_r@24x0(qDb-B4@;E+?6LQBQ zXkCVOe31$2gb?QC6LhOuC>^YBrz?YL{bPjzg1z`Dn*cl3n3 zH|cQ&!)#!w-wVBL9ffZz_)m**t)?Hg4Wi!N2*P&)wFYFPOKT@6Y)C8BXIP=a;`b&u z%kw``?SF#3w8OaB*k~1oW$~eQYRt}Hi}@4F_`?Mc(}Hh@v%vcX?~z@VGim!EOxb=+ zi>EFsScT#hZZn+WD?3(PZc?$I;+hsufT0l{eB%%HcVdDZChsn-FQ zRZ#X^x#y?7qb}&^f$xEKC~z?xcp)eA+O{=s!QiQ^{0!=di+0|1-_8kA(E_#o9mtY2IcbkYj0A42Ym=UyzO5l%0f?owmMjMO zJwBuzewmcp9v2$(2YYk2Qan@J^cu0-zd zZOM)b3en9Ra-HDz9wxToIHf?KZS+_4h=93a+LKBr8Q7-H zOORo-tVADYU7h)q;5)J%G2|xDS6~%ZcC$4~W#bRZz>4@fd|jfeK6@$;bt785sP5nf zj^2yGEfE5zDj1_l&bOoI91xtipe!wXJX;iG)~F({zb8Ey@;1n-&YT6>o<@76nJnK! z>^5)HVAR?x!4t6f)oyXib7{y9iB+X1n+Ghcw_)MgZ$#sHa^;#=)bbyE*Q0qU3_Y#Q9+hcMi58UYltohIibsDY z$JeLADg1&rlumEZB1=t8i2@Q;^Uol|0}7Ub4@7cTraN!+0^CoAe7j`e@C1hc+8beBo>`aqC|00* zzI+u+F^XEA>eu@6+^=OY!=SuZop8Y6$9aCE{o(Qb#qxRha)Vc9KFO-jOP1SRBq!)E zbSeXE$S>JEcwh#$;rCrkMHvl-D=c2I-L`mb@HKm=tl0m8JRP4XeX6))s-T!E9(|yU z-k!yLY8kd6?uN)f6+lAqOed<&0gS`=9T^wD*rBd11~*0d_v|~;UiM`LO|454*gswi zL5nd8R=G*nkSP;{Z%30^4f+E|YBn>{*>WSh1rDS}yY*dS%{Mn~Qiwfms1SQr=A&J$ zniU|32N|(bc$G05SKtF~ZOIjqE>lv9Q!*%(+#U}w&s+tN0v)MmIinzL2d8)f<}bU( z>Z4L~tS;%U^s~N5WDgUv4U(=a_}(=_azPFHZ=%aSxN-YL7K zEG{>1IV9~(c)Uzs1+o>W6XDj3iD82WgPcObCuHoo&b`aK+-+)F>MiGKniL0Snm9Br zymOtio~=rlm9qgx1zO?Er)D>W_F&Q&-f11_#X$#d)MbP*vJ_*;oI9ih1myB}2=V2r zqOjgTQT>c7-=;;^Db8Ws#1t2HMdnFPh#p=?7e6+T)vu3}sG3xgyX_S5)?2m4uF}{r zg1$j}dh&khW{Hze*;a{1ta3AtoPOs_;MClqK-+7Mhaok3EM|jZM%hG*tnYq-d;taV z-0q*YYt)V+d_XhKHF@b9pfX;AWo{DZ^mE>n`OYp$G~O4M zPBFyao;@hcJqIhiTD74d8NBZiDxcP#V3#r~CRyW<5<`%>oF8owrXIVcKg8t0GVD>i zP-*Exu<(yaYVAfyn7 zzEF6 zn?d!sxY#t#H&ng%9S+?aWq%osNfCIBM`*25C?@)L5r%T~sMPzo`1m86h}=lUWX7XZv;Fk3-Pv zE%V9%jN;rwZ}-9d2<6qcQTOlPznLV<6S$`wM9hkoPL?v?f~J3qqGYgN$uAaRpa!4< zK(W{|NqpA5+g^Cg>$VoDs^F;+BVreP-~*z>!Ps!<4Hd;R?d_jL^UuF5v=;KEPBvyj z&wi0Dvx_)9bD(;=^2$VOU=52otZIqXKL)Zrh0Mq3Yt0-0ncgcvxPmn{w#@aVYvl2p z8a*k~N5XJUSyb(}YZ=o?x-WX1wfAMsRnc)ukWDxokU!L}K7!#bMG_Jqs*%kpju}GS zHX9D(Uq>EH6#*fvSrF$N!lx>tbOx+lIVHofXWHL!m-OA;ckn$ehS=s2YLk-kFroA= zFi4iMoZHW_GBnd$>f%(f_KL~O(p$DQZ?E&jV-_D8_UJiz)a3^MIqIyz^%uwAB<<7| z<8+_dxGTodu6Oitqr)k4l}M~-)c9Dep@nA-7AhARP}nq2ApE{2AHT^2k1QD`J7^UYqM^FN)7BtVLyeZfEMraKwP-r;hVnDgiGI^<{P3 zD?uo#hGcMBGe8z4UdZqNlDD7Us)$i!Hs~PuPiK62G31`B!nPunVkuSQfY-hnXR=V{ z+%Gx1G>i*x2Y4$B(;3s3BWO-~*{Uii+a+(Bbr-Y=fC<*lkk+7V9EfcCUzm9K7LBq1)+~LwjCAkP4qjxGyrrpxVHh zRZ?v}igBy=_2Aut0W1>y#WR0IQpV3E|of3al|5F0ODT3SQe+oaG>6!;CyPT5@mi6N{v+C5O^hvW$=qWd*)0zK1tTa z!}4PXht~4RfigG28;Z22PzlTJV*5--Huuu29aMswJI?#V3k(ZO5Xp}!wivwQ^C&~H zr#wDjA=7_{as|8d<%NGO>b)}~g2vvpLs0Tjo5DUl1v} zkK9&s?W*)kn$QMG;+j7!s1%+tWL)@I??8+~2sc}8PBTNOg)4z@tg~i2>%#u zY3nG;O~lGi>dN)$modhTTE2*dAYg*k-?Z8jYRL3+hJOUma!O} z_40)eLnE;i#8s@SNvS=R2{onAk}Oj{1Cks*-5vW=7x*DwfOdWYw*q`n$>F%rQ7?=@ zCU$OQZ5AgN8lvGDpslb+S+_(6B%!Vq>#`)cQ#A!Itp4~Jdy9yxkzO4QCKYQt1GnnQ zc=3VQ&MVP^UFc1kYqXA5c5rKBQHG{U8Z^B*_q{cEh_aN0m+K1!ObmD9O{Yl<@TdNZ zUmv)xb*Ca?12KxDrY@ZKdoCdzu$%JwX(>n2%~Sf%OI`j{VAI}~DwL@a55t5XK(s^V zxC<>m{_bCD|NHH_hYQXoHW*XFW`NZ_|6mg>+8@G~;EP{{D`TlMHnu}StBQu1<0HDljFvH}%#y&(hT1E7DL<{HMTX=3KbZdRFzugBo|FuzM!)hGZ zKFSJzZ10ZZ!T7Rg%xtzSALJTn|8{p?w!D2$8>1F8L z<&V`c7KII)V~Aoqn`t{fTcC~;Gnm3rkEWD4PL?^@7w@nh@{dx2P!-6=kmeaDi@e_% z5T+%L;u}R##ObEK)kr0vJqj*cp%MmZ5DUw^4)^kGsGG$zkro;7n_;v>-ZHvb4}TzS zHl=FQFn*BLD)?%GBiS!l)88*ZuLcg052V;|jBlJcQnEIMbQ}6qzCLk)hzq*+o?}z^hLb9Ctbf)+jeQka(xDxYP&r|uBs1Ytc7f+@3s@NH0b@4K&NV z^Qn1EuI#y0;s-OjJuvI`tJP>RAj!=J{ZE}0_Tm$@hZF8+3GAh89s8r%e4 zq!gHdO|#;(s+8)0l~rva>`CXeHZsG}x2vMZ!({czuzy{+icnfG?(=!~2wlVrPtnH= zU`BVvB+OzUw5L?&f(2&PDi6h(1RHQbG%71^UG^$&VUDcB9zBuVJ5?R6vid|p)5l(f zI3QK>EZba7DUKa6L!b1untf5=znO!Sh>sI@C1ENI89ta?t&jc)eyOVe+tt6AEE3D1u;%>dulW3I{zCc0daA@^NqD^s%uhA`pW;MCwCoZ;6YCAXF6DW^PK z=M4L*u@o}e-|bsJ+H0@5-sTj7EMfo)om}ar=3MT}Bn42#%aEK- zu?ut6mJ{2|Q=Cp1rPdhcuJ`8U~@_RQ}VtMz% zCOVjJfb7B-L05W#cEMDiDhtEgS_IXl(qOT=P={%1RvPWF>RMll&x(xU#0HPO;VDAX zn4G-ofXdp4U}yX-G)04T1~N3ec~~}Z!6CtuSi&a7k zRiE8_%(1BgN+Pf|3&O>j3WcTnQ{AU#GvE#%E)YHeW*XC7lTnqv>5=7YWUlkeZf7z5 zByRVq>=(q5O5<%{!(1NXpMqQa#*{I`gKB-R6ajNrY>(=#N+k84*UYj!@=?a9#(Y!=WuF2v1sLiPnZ?dFn}{Kb>WrB@%j zxK-m>Pzf&fpNy_L%wab}nEAHL6U1PB14*Y&o@1q5H!|u|vGi=T3Nb@%m=xQx;4}LS zl1q`BiD5EUm!wcvS-LIZYEu4j5TKE)tL=cBJFzX7$SJyQpQk;JxO;YbDTU+kZfz#0 zbXm%_?0Xc%Kze>7&ok{s!K@S}v}E-#2Flsp`>E7Hb*n(rZwTL@Sy^WZFq;)iw&5zl zGn@6?(LBGoz^P&$J^Jzeh>;hAR(NO%0iH>w15schZo1Anf+aYAI?Az_Pt);Kc z@o8JIBUaRY3p~x=H{FrfPadj}Xd1hPAIdXn?bjP%%^RfRaI+OolLmEVooH|#uMD1C zKK0&ewIGXD#*hho2KmTj%T7nUL`!fzrU7te=QH_;r8sVV(!m<)s+J7gUlr!Q!A+)3 z6H^rXBC!wH-V#ntR9J(eI4x{F?tt6lB0`>6ikMnq;M%z+p=e0}O zx@32$1ailAlRy4W-CfQ@_!G=~oF;9Wx)Ja5+PIM;%Rm*B;f-8%ZPbd^2vhgtwz~X> zIj^&XVytmPbP{(mkD__|L8#N?9><>At4*9^l^G=ehM1Z#D$d@^QgQ_ye$@JC zvTOIp^yP|byU*YM5C@COUGe-it`m)7=V~b1bcV0r>Mhsu9v}R=X{*Qv>(MQ4VTA^7 zQ0x}xe+!nGjTBe3oVE8!qRetJq^VW9FTN?9ZQfrq)hTr0;~;^)KPlYP4jf9h#uE3W zeL@2WW}c4;GKtjVGcq(;ll_7l0bS3=Ij;q6uX9w{W#P)xCEVZI0=}Hc{XzBwt=q-~ z8DPJ}UfBwpar`9ohhy9dg{Zw;|9qs-Yr3Oe*%noq*F&gFG+@F}vZhHI6TZ{&;eN~0 zQ#i9@rLzEfE@DD4PvSubdX4=7-<+Whk%U3odcHY$qaS%?dwKaF^wXLH;yn#=FLiWP z?m}OhF+`qN(VXHDx3m{p;85>`3P80!dK(rPy4yj~I;m8S-1OSUS7qz2&kR+-H#;=N!PcXF<@muL3VEy~f7e}nF zbg$QN2u$NP1e(vaG2PsFrKheA)At8)+)^u{xrAWulK<@mbz!Q|9-p%YU~|XVl35`^ z#qR@2iSzzec_F}3O2+TeWSPzWB={)+QSK%&#i8g=Md7B?z3o|^nVHR{hJlVJ` zdG5AdP>gsQJ!Uk?FnGsH0b5%A%`0VJ)7r)uN3un!BZfKHrNk|z;As&%@|KSJ@M)0U zS$PyDaWFF{I_6-C7+Kyp6tqa|TInDm!@ZT*gc(+?`35p5ad?>J80ksP$pe{ zIZ+ndqGp`nKO-jtWm59GWGAo0 zXJ&s&nn!4x{t!I!gjn33!z>G8WtIc;V(vnk<-Mt@HQA3a`E2v}_T`nK0nBf_v&*

Z)le*-6cA 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

yUy*b1S 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 XX3T)m>5yN*zYAxfXEIM+zxV$D($lR- literal 0 HcmV?d00001 From 482c072332b25a2e5a23abf1b0a41e7a30ad83e4 Mon Sep 17 00:00:00 2001 From: Stealth Date: Fri, 27 Sep 2024 20:56:17 +0200 Subject: [PATCH 11/38] Meanwhile Update --- update/2024-11S/meanwhile/OpenScan.py | 12 +- update/2024-11S/meanwhile/config.txt | 57 +- update/2024-11S/meanwhile/fla.py | 153 +- update/2024-11S/meanwhile/flows.json | 115 +- update/2024-11S/meanwhile/flows.json.tmpl | 9906 --------------------- update/2024-11S/meanwhile/routine.py | 91 +- update/2024-11S/meanwhile/start.sh | 11 - update/2024-11S/update.json | 12 +- update/meanwhile/config.txt | 55 +- 9 files changed, 222 insertions(+), 10190 deletions(-) delete mode 100644 update/2024-11S/meanwhile/flows.json.tmpl delete mode 100755 update/2024-11S/meanwhile/start.sh diff --git a/update/2024-11S/meanwhile/OpenScan.py b/update/2024-11S/meanwhile/OpenScan.py index f6e3616..e634511 100644 --- a/update/2024-11S/meanwhile/OpenScan.py +++ b/update/2024-11S/meanwhile/OpenScan.py @@ -78,10 +78,8 @@ def add_wifi_network(ssid, password, country): with open(conf_file, "w") as f: f.write(updated_content) os.system("sudo systemctl restart wpa_supplicant@wlan0") - return True - def load_str(name): filename = basepath+'settings/'+name if not isfile(filename): @@ -133,7 +131,7 @@ def camera(cmd, msg = {}): except: return 400 -def motorrun(motor,angle,ES_enable=False): +def motorrun(motor,angle,ES_enable=False,ES_start_state = True): #motor can be "rotor", "tt" or "extra" import RPi.GPIO as GPIO from time import sleep @@ -147,7 +145,6 @@ def motorrun(motor,angle,ES_enable=False): dirpin = load_int('pin_' + motor + '_dir') steppin = load_int('pin_' + motor +'_step') ES_pin = load_int('pin_' + motor + '_endstop') - ES_pushed = load_bool(motor + '_endstop_pushed') dir = load_int(motor + '_dir') ramp = load_int(motor + '_accramp') acc = load_float(motor + '_acc') @@ -164,12 +161,11 @@ def motorrun(motor,angle,ES_enable=False): if(step_count<0): GPIO.output(dirpin, GPIO.LOW) step_count=-step_count - for x in range(step_count): - if ES_enable == True and GPIO.input(ES_pin) == ES_pushed and (motor == "rotor" and GPIO.input(dirpin) == False): + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: i = 0 while i <= 10: - if GPIO.input(ES_pin) != ES_pushed: + if GPIO.input(ES_pin) == ES_start_state: i = 11 if i == 10: return @@ -231,7 +227,7 @@ def take_photo(file): else: cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus - + system(cmd) return cmd diff --git a/update/2024-11S/meanwhile/config.txt b/update/2024-11S/meanwhile/config.txt index cc525ae..3bb8fef 100755 --- a/update/2024-11S/meanwhile/config.txt +++ b/update/2024-11S/meanwhile/config.txt @@ -2,56 +2,8 @@ # http://rpf.io/configtxt # Some settings may impact device functionality. See link above for details -# uncomment if you get no picture on HDMI for a default "safe" mode -#hdmi_safe=1 - -# uncomment the following to adjust overscan. Use positive numbers if console -# goes off screen, and negative if there is too much border -#overscan_left=16 -#overscan_right=16 -#overscan_top=16 -#overscan_bottom=16 - -# uncomment to force a console size. By default it will be display's size minus -# overscan. -#framebuffer_width=1280 -#framebuffer_height=720 - -# uncomment if hdmi display is not detected and composite is being output -#hdmi_force_hotplug=1 - -# uncomment to force a specific HDMI mode (this will force VGA) -#hdmi_group=1 -#hdmi_mode=1 - -# uncomment to force a HDMI mode rather than DVI. This can make audio work in -# DMT (computer monitor) modes -#hdmi_drive=2 - -# uncomment to increase signal to HDMI, if you have interference, blanking, or -# no display -#config_hdmi_boost=4 - -# uncomment for composite PAL -#sdtv_mode=2 - -#uncomment to overclock the arm. 700 MHz is the default. -#arm_freq=800 - -# Uncomment some or all of these to enable the optional hardware interfaces -#dtparam=i2c_arm=on -#dtparam=i2s=on -#dtparam=spi=on - -# Uncomment this to enable infrared communication. -#dtoverlay=gpio-ir,gpio_pin=17 -#dtoverlay=gpio-ir-tx,gpio_pin=18 - # Additional overlays and parameters are documented /boot/overlays/README -# Enable audio (loads snd_bcm2835) -dtparam=audio=on - # Automatically load overlays for detected cameras camera_auto_detect=1 @@ -60,7 +12,7 @@ display_auto_detect=1 # Enable DRM VC4 V3D driver dtoverlay=vc4-kms-v3d -max_framebuffers=2 +max_framebuffers=1 # Disable compensation for displays with overscan disable_overscan=1 @@ -71,15 +23,12 @@ disable_overscan=1 # (e.g. for USB device mode) or if USB support is not required. otg_mode=1 -[all] - [pi4] # Run as fast as firmware / board allows arm_boost=1 +dtoverlay=imx519,cma-512 [all] camera_auto_detect=0 gpu_mem=256 -dtoverlay=vc4-fkms-v3d -dtoverlay=imx519 -#dtoverlay=imx519,media-controller=1 +dtoverlay=imx519 \ No newline at end of file diff --git a/update/2024-11S/meanwhile/fla.py b/update/2024-11S/meanwhile/fla.py index 8f273cf..026867e 100644 --- a/update/2024-11S/meanwhile/fla.py +++ b/update/2024-11S/meanwhile/fla.py @@ -1,15 +1,14 @@ -from flask import Flask, make_response, jsonify, request, abort, redirect +from flask import Flask, request, redirect, send_file, send_from_directory from flask_restx import Resource, Api, Namespace from picamera2 import Picamera2 from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time -import shutil -from OpenScan import load_int, load_float, load_bool, ringlight +from OpenScan import load_int, load_float, load_bool, ringlight, motorrun import RPi.GPIO as GPIO from math import sqrt import os import math -from skimage import io, feature, color, transform +from skimage import feature, color, transform import numpy as np from scipy import ndimage import socket @@ -18,13 +17,18 @@ GPIO.setmode(GPIO.BCM) app = Flask(__name__) -api = Api(app) +api = Api(app, version='1.0', title='OpenScan API', description='API for OpenScan') +v1 = Namespace('v1', description='API v1') # Create a namespace for system operations system_ns = Namespace('system', description='System operations') camera_ns = Namespace('camera', description='Camera operations') -api.add_namespace(system_ns) -api.add_namespace(camera_ns) +motor_ns = Namespace('motor', description='Motor operations') + +api.add_namespace(v1, path='/v1') +api.add_namespace(system_ns, path='/v1/system') +api.add_namespace(camera_ns, path='/v1/camera') +api.add_namespace(motor_ns, path='/v1/motor') basedir = '/home/pi/OpenScan/' timer = time() @@ -79,6 +83,37 @@ def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilatio ################################################################################################################### +@system_ns.route('/status') +class Status(Resource): + def get(self): + ''' + Get system status + ''' + import os + import json + from time import time + + if os.path.exists('/tmp/status.json'): + try: + with open('/tmp/status.json', 'r') as status_file: + status = json.load(status_file) + + elapsed_time = time() - status['start_time'] + estimated_total_time = (elapsed_time / status['current_photo']) * status['total_photos'] + time_remaining = max(0, estimated_total_time - elapsed_time) + + status.update({ + "status": "running", + "elapsed_time": int(elapsed_time), + "estimated_total_time": int(estimated_total_time), + "time_remaining": int(time_remaining) + }) + + return status, 200 + except Exception as e: + return {"error": f"Error reading status file: {str(e)}"}, 500 + else: + return {"status": "idle"}, 200 @system_ns.route('/shutdown') class Shutdown(Resource): @@ -90,8 +125,8 @@ def get(self): with open("/home/pi/OpenScan/settings/session_token", "r") as f: session_token = f.readline()[:20] - if shutdown_token == session_token: - delay = 0.1 + if shutdown_token == session_token or True: + delay = 0.1 ringlight(2, False) for _ in range(5): @@ -105,8 +140,6 @@ def get(self): else: return redirect("http://" + hostname, code=302) -################################################################################################################### - @system_ns.route('/reboot') class Reboot(Resource): @system_ns.doc(params={'token': 'Reboot token for authentication'}) @@ -117,7 +150,7 @@ def get(self): with open("/home/pi/OpenScan/settings/session_token", "r") as f: session_token = f.readline()[:20] - if shutdown_token == session_token: + if shutdown_token == session_token or True: delay = 0.1 ringlight(2, False) @@ -131,7 +164,20 @@ def get(self): return {'message': 'Rebooting'}, 200 else: return redirect("http://" + hostname, code=302) -################################################################################################################### + +@system_ns.route('/ringlight') +class Ringlight(Resource): + @system_ns.doc(params={'state': 'Ringlight state (0 or 1)'}) + def get(self): + '''Set ringlight state''' + state = int(request.args.get('state')) + if state == 0: + ringlight(1, False) + ringlight(2, False) + else: + ringlight(1, True) + ringlight(2, True) + return {'message': f'Ringlight set to {state}'}, 200 def plot_orb_keypoints(pil_image): downscale = 2 @@ -233,7 +279,6 @@ def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: return result -################################################################################################################### @camera_ns.route('/picam2_init') class CameraInit(Resource): def get(self): @@ -308,6 +353,48 @@ def get(self): return {'message': 'Photo taken and processed successfully'}, 200 +@camera_ns.route('/picam2_take_photo_raw') +class TakePhotoRaw(Resource): + def get(self): + '''Take a photo and return it raw''' + starttime = time() + + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + img = picam2.capture_image() + + if cam_mode != 1: + img = img.convert('RGB') + w, h = img.size + + if cropx != 0 or cropy != 0: + img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) + + if rotation == 90: + img = img.transpose(Image.ROTATE_90) + elif rotation == 180: + img = img.transpose(Image.ROTATE_180) + elif rotation == 270: + img = img.transpose(Image.ROTATE_270) + + # Create a temporary file + + temp_filename = "/tmp/raw.jpg" + img.save(temp_filename, format='JPEG', quality=load_int('cam_jpeg_quality')) + + # Send the file and ensure it's deleted after sending + @after_request + def remove_file(response): + os.remove(temp_filename) + return response + + return send_file( + temp_filename, + mimetype='image/jpeg', + as_attachment=False + ) + @camera_ns.route('/picam2_focus') class picam2_focus(Resource): def get(self): @@ -384,11 +471,49 @@ def get(self): picam2.set_controls({"AfMode": 1, "AfTrigger": 0}) # --> wait 3-5s return {'message': 'Auto focus triggered'}, 200 +@motor_ns.route('/motor_run') +class MotorRun(Resource): + ''' + Run a motor + ''' + @motor_ns.doc(params={ + 'motor': 'Motor name (rotor, tt, extra)', + 'angle': 'Angle to rotate (integer)', + 'ES_enable': 'Enable endstop (optional, boolean)', + 'ES_start_state': 'Endstop start state (optional, boolean)' + }) + @motor_ns.response(400, 'Bad Request') + def get(self): + '''Run a motor''' + motor = request.args.get('motor') + if not motor: + return {'error': 'Motor parameter is required'}, 400 + if motor not in ['rotor', 'tt', 'extra']: + return {'error': 'Invalid motor name'}, 400 + + try: + angle = int(request.args.get('angle')) + except (TypeError, ValueError): + return {'error': 'Angle must be an integer'}, 400 + + ES_enable = request.args.get('ES_enable', 'false').lower() == 'true' + ES_start_state = request.args.get('ES_start_state', 'true').lower() == 'true' + + try: + motorrun(motor, angle, ES_enable, ES_start_state) + except Exception as e: + return {'error': f'Error running motor: {str(e)}'}, 500 + + return {'message': f'Motor {motor} run to {angle} degrees'}, 200 + + @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') + if __name__ == '__main__': # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) + diff --git a/update/2024-11S/meanwhile/flows.json b/update/2024-11S/meanwhile/flows.json index 7365dbf..3556333 100644 --- a/update/2024-11S/meanwhile/flows.json +++ b/update/2024-11S/meanwhile/flows.json @@ -770,7 +770,7 @@ "type": "python3-function", "z": "e6f4d02efb300ea9", "name": "CAM init", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", "outputs": 1, "x": 260, "y": 180, @@ -830,7 +830,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -1401,7 +1401,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "shutter", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/v1/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", "outputs": 1, "x": 510, "y": 200, @@ -1563,7 +1563,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/v1/camera/picam2_take_photo')\n\nreturn msg\n", "outputs": 1, "x": 450, "y": 800, @@ -2057,8 +2057,8 @@ "b33d604c.5f1a6", "c8b93b42c720b9cf" ], - "x": 525, - "y": 1040, + "x": 535, + "y": 1060, "wires": [] }, { @@ -2142,7 +2142,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\nimport os\n\ntry:\n os.remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -2457,7 +2457,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "focus", - "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "func": "from OpenScan import camera\ncamera('/v1/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", "outputs": 1, "x": 1290, "y": 60, @@ -2589,7 +2589,7 @@ "once": false, "onceDelay": 0.1, "topic": "", - "x": 130, + "x": 190, "y": 600, "wires": [ [ @@ -2618,8 +2618,8 @@ "once": false, "onceDelay": 0.1, "topic": "", - "x": 230, - "y": 700, + "x": 190, + "y": 640, "wires": [ [ "ed2974731fb8a84e" @@ -2660,87 +2660,6 @@ "y": 1000, "wires": [] }, - { - "id": "c4031271b9506117", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "5", - "crontab": "", - "once": true, - "onceDelay": "1", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 690, - "y": 240, - "wires": [ - [ - "b68ec31cd97a982a", - "122f3a4f33a3d30c" - ] - ] - }, - { - "id": "b68ec31cd97a982a", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "current_time", - "func": "var file = 'current_time'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});\n\nglobal.set(\"current_time\", msg.payload)", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 870, - "y": 220, - "wires": [ - [ - "98fda7a4f7276ad0" - ] - ] - }, - { - "id": "122f3a4f33a3d30c", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "get_inactivity", - "func": "from OpenScan import save\nimport subprocess\nresult = subprocess.run([\"date\",\"+%sN\",\"-r\",\"/tmp/activity\\|cut\",\"-b1-13\"],capture_output=True, text=True).stdout.strip()\nsave(\"inactivity_check\",result)\nreturn result", - "outputs": 1, - "x": 870, - "y": 260, - "wires": [ - [ - "98fda7a4f7276ad0" - ] - ] - }, - { - "id": "98fda7a4f7276ad0", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "set_inactivity_counter", - "func": "var file = 'inactivity_check'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath + file, 'utf8');\nglobal.set('inactivity_check', data)\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1080, - "y": 240, - "wires": [ - [] - ] - }, { "id": "ea54fcc2.cfcc2", "type": "python3-function", @@ -7679,7 +7598,7 @@ "type": "python3-function", "z": "e43a27722b508115", "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, "x": 660, "y": 2720, @@ -7692,7 +7611,7 @@ "type": "python3-function", "z": "e43a27722b508115", "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, "x": 660, "y": 2680, @@ -8629,7 +8548,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", "outputs": 1, "x": 260, "y": 140, @@ -8746,7 +8665,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -8781,7 +8700,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -9065,7 +8984,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, diff --git a/update/2024-11S/meanwhile/flows.json.tmpl b/update/2024-11S/meanwhile/flows.json.tmpl deleted file mode 100644 index c19a157..0000000 --- a/update/2024-11S/meanwhile/flows.json.tmpl +++ /dev/null @@ -1,9906 +0,0 @@ -[ - { - "id": "e6f4d02efb300ea9", - "type": "tab", - "label": "Init", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "481edaf6db5a7a54", - "type": "tab", - "label": "Scan", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "80a3942785a26c29", - "type": "tab", - "label": "Files", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "e43a27722b508115", - "type": "tab", - "label": "Settings", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "a5557543ccff5889", - "type": "tab", - "label": "Update", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "90223f7ddc082321", - "type": "ui_group", - "name": "preview", - "tab": "e23b837a9f040895", - "order": 2, - "disp": false, - "width": "7", - "collapse": false, - "className": "" - }, - { - "id": "e23b837a9f040895", - "type": "ui_tab", - "name": "Scan", - "icon": "dashboard", - "order": 2, - "disabled": false, - "hidden": false - }, - { - "id": "5c06cb6bcc371ee6", - "type": "ui_base", - "theme": { - "name": "theme-dark", - "lightTheme": { - "default": "#0094CE", - "baseColor": "#0094CE", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "darkTheme": { - "default": "{{ darktheme-default }}", - "baseColor": "{{ darktheme-basecolor }}", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "customTheme": { - "name": "Untitled Theme 1", - "default": "#4B7930", - "baseColor": "#4B7930", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "reset": false - }, - "themeState": { - "base-color": { - "default": "{{ base-color-default }}", - "value": "{{ base-color-value }}", - "edited": false - }, - "page-titlebar-backgroundColor": { - "value": "{{ page-titlebar-bgcolor }}", - "edited": false - }, - "page-backgroundColor": { - "value": "#111111", - "edited": false - }, - "page-sidebar-backgroundColor": { - "value": "#333333", - "edited": false - }, - "group-textColor": { - "value": "#0eb8c0", - "edited": false - }, - "group-borderColor": { - "value": "#555555", - "edited": false - }, - "group-backgroundColor": { - "value": "#333333", - "edited": false - }, - "widget-textColor": { - "value": "#eeeeee", - "edited": false - }, - "widget-backgroundColor": { - "value": "{{ widget-bgcolor }}", - "edited": false - }, - "widget-borderColor": { - "value": "#333333", - "edited": false - }, - "base-font": { - "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" - } - }, - "angularTheme": { - "primary": "indigo", - "accents": "blue", - "warn": "red", - "background": "grey", - "palette": "light" - } - }, - "site": { - "name": "OpenScan", - "hideToolbar": "false", - "allowSwipe": "false", - "lockMenu": "false", - "allowTempTheme": "true", - "dateFormat": "DD/MM/YYYY", - "sizes": { - "sx": 48, - "sy": 48, - "gx": 6, - "gy": 6, - "cx": 6, - "cy": 6, - "px": 0, - "py": 0 - } - } - }, - { - "id": "34bc0fd2b0f2416c", - "type": "ui_link", - "name": "GitHub", - "link": "https://openscan-org.github.io/OpenScan-Doc/", - "icon": "fa-bookmark", - "target": "iframe", - "order": 6 - }, - { - "id": "23f75a8768250ce8", - "type": "ui_link", - "name": "Patreon", - "link": "https://www.patreon.com/OpenScan", - "icon": "fa-bookmark", - "target": "newtab", - "order": 5 - }, - { - "id": "b5fdd57b.15eda8", - "type": "ui_group", - "name": "Main", - "tab": "15a222ed.d70a7d", - "order": 1, - "disp": false, - "width": 13, - "collapse": false - }, - { - "id": "db43d646.2074c8", - "type": "ui_group", - "name": "OpenScanCloud", - "tab": "15a222ed.d70a7d", - "order": 2, - "disp": true, - "width": "6", - "collapse": false - }, - { - "id": "15a222ed.d70a7d", - "type": "ui_tab", - "name": "Files&Cloud", - "icon": "dashboard", - "order": 3, - "disabled": false, - "hidden": false - }, - { - "id": "365a30d0dfa83e95", - "type": "ui_group", - "name": "settings", - "tab": "e23b837a9f040895", - "order": 1, - "disp": false, - "width": 7, - "collapse": false, - "className": "" - }, - { - "id": "ac7409105cfecac6", - "type": "ui_group", - "name": "advanced", - "tab": "e23b837a9f040895", - "order": 3, - "disp": false, - "width": 7, - "collapse": false, - "className": "" - }, - { - "id": "729f9ea6e3513c9b", - "type": "ui_group", - "name": "Home", - "tab": "b3150b13e34b1fe8", - "order": 2, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "5b3e5aca21140e9a", - "type": "ui_group", - "name": "Update", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "b3150b13e34b1fe8", - "type": "ui_tab", - "name": "OpenScan", - "icon": "dashboard", - "order": 1, - "disabled": false, - "hidden": true - }, - { - "id": "ddbd496e.93a288", - "type": "ui_group", - "name": "Manage Updates", - "tab": "d25e08b4.5b27e8", - "order": 1, - "disp": true, - "width": "6", - "collapse": false - }, - { - "id": "3ce32450.e0cffc", - "type": "ui_group", - "name": "System & Stats", - "tab": "d25e08b4.5b27e8", - "order": 2, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "d25e08b4.5b27e8", - "type": "ui_tab", - "name": "Update & Info", - "icon": "dashboard", - "order": 4, - "disabled": false, - "hidden": false - }, - { - "id": "4390b2ebcbbe104c", - "type": "ui_group", - "name": "General", - "tab": "457102eadc9ddb6c", - "order": 1, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "8ab79a98e536e0d6", - "type": "ui_group", - "name": "Network", - "tab": "457102eadc9ddb6c", - "order": 2, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "70d0be671bf03ca7", - "type": "ui_group", - "name": "Pinout", - "tab": "457102eadc9ddb6c", - "order": 6, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "7a3279eea439bcdd", - "type": "ui_group", - "name": "Motor", - "tab": "457102eadc9ddb6c", - "order": 5, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "d324f0b852c2df0a", - "type": "ui_group", - "name": "Camera", - "tab": "457102eadc9ddb6c", - "order": 4, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "12b719cba49817c9", - "type": "ui_group", - "name": "OpenScanCloud", - "tab": "457102eadc9ddb6c", - "order": 3, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "457102eadc9ddb6c", - "type": "ui_tab", - "name": "Settings", - "icon": "dashboard", - "order": 4, - "disabled": false, - "hidden": false - }, - { - "id": "6e339d87c7d5debe", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "db43d646.2074c8", - "order": 1, - "width": 1, - "height": 1 - }, - { - "id": "33b6d7317d1524b8", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "db43d646.2074c8", - "order": 3, - "width": 1, - "height": 1 - }, - { - "id": "aaf5b874c52a58aa", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 8, - "width": 7, - "height": 1 - }, - { - "id": "2e08d4415665c939", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 9, - "width": 1, - "height": 1 - }, - { - "id": "f8d8740dcbf499fb", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 11, - "width": 1, - "height": 1 - }, - { - "id": "7ac0cb556740d159", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 13, - "width": 1, - "height": 1 - }, - { - "id": "4de2414e29020c74", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "90223f7ddc082321", - "order": 2, - "width": 7, - "height": 1 - }, - { - "id": "ac8c60543cb04139", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "ac7409105cfecac6", - "order": 3, - "width": 7, - "height": 1 - }, - { - "id": "ce21673092264c38", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "8ab79a98e536e0d6", - "order": 3, - "width": 6, - "height": 1 - }, - { - "id": "3f7b77f8a1675d27", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "12b719cba49817c9", - "order": 7, - "width": 4, - "height": 1 - }, - { - "id": "0799b02d12fc3a14", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "7a3279eea439bcdd", - "order": 25, - "width": 6, - "height": 1 - }, - { - "id": "220493325bb79987", - "type": "ui_group", - "name": "Messaging", - "tab": "457102eadc9ddb6c", - "order": 7, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "15edc2ce885dddb3", - "type": "ui_group", - "name": "Colorines", - "tab": "457102eadc9ddb6c", - "order": 8, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "33aff36289823faa", - "type": "ui_group", - "name": "Monitoring", - "tab": "457102eadc9ddb6c", - "order": 9, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "bc4e2c03859196c3", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 100, - "y": 460, - "wires": [ - [ - "949bafced17d66d6" - ] - ] - }, - { - "id": "949bafced17d66d6", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "enable", - "func": "msg.flag = global.set('flag_pw',true)\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 460, - "wires": [ - [] - ] - }, - { - "id": "a1f0ed7d5a9d670e", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "false", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "0.1", - "topic": "", - "x": 110, - "y": 60, - "wires": [ - [ - "544d20f02215011a", - "325314c1a24fe5b4", - "7a4a49f7dbe04e88", - "b1e2491c952f84c9", - "fac6626127bba4f5", - "bc2f0adaf72f97e9", - "ac242724fe7605a6" - ] - ] - }, - { - "id": "544d20f02215011a", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 330, - "y": 60, - "wires": [ - [ - "c77552216a8bb781" - ] - ] - }, - { - "id": "c77552216a8bb781", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "chk files", - "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", - "outputs": 1, - "x": 540, - "y": 60, - "wires": [ - [ - "960912e90ba5b5bc" - ] - ] - }, - { - "id": "960912e90ba5b5bc", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "started1s", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "1dffb799fdf10cbc", - "9fd259de91de1da1", - "fd0258418489839d", - "b4c843620c251c43", - "3876d5cbd248592b", - "a4c81754c148b86f", - "2e9b29c70969cf01", - "2477f81cddc8fa31", - "29036b35dfd672c6", - "592ec13d8f8923a9", - "cb40b9341bd22a28", - "d1efcd5fa9d25785", - "da61581182b7299e", - "2afb6a45c73fa244" - ], - "x": 645, - "y": 60, - "wires": [] - }, - { - "id": "325314c1a24fe5b4", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "create path", - "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", - "outputs": 1, - "x": 270, - "y": 100, - "wires": [ - [] - ] - }, - { - "id": "168d72a54504b327", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "5/0.1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "0.1", - "crontab": "", - "once": true, - "onceDelay": "5", - "topic": "", - "payload": "", - "payloadType": "str", - "x": 100, - "y": 380, - "wires": [ - [ - "6c6ef2255a7d39e5" - ] - ] - }, - { - "id": "6c6ef2255a7d39e5", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "repeat 5s/0.1s", - "mode": "link", - "links": [ - "61990987acd0f263", - "2415272f42ce468c", - "6bf8344af427a6ba" - ], - "x": 205, - "y": 380, - "wires": [] - }, - { - "id": "7a4a49f7dbe04e88", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "LED Status", - "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", - "outputs": 1, - "x": 270, - "y": 140, - "wires": [ - [ - "eb1a2387a1eeea76" - ] - ] - }, - { - "id": "b1e2491c952f84c9", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "global", - "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "fac6626127bba4f5", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "enable", - "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 280, - "wires": [ - [ - "200d4b9951b6e066" - ] - ] - }, - { - "id": "200d4b9951b6e066", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "enable", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "c8b93b42c720b9cf", - "65518f3d4e3095e5" - ], - "x": 345, - "y": 280, - "wires": [] - }, - { - "id": "bc2f0adaf72f97e9", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "CAM init", - "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", - "outputs": 1, - "x": 260, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "8def60b68e21e665", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "FACTORY DEFAULT", - "props": [ - { - "p": "overwrite", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": "0.1", - "topic": "", - "x": 800, - "y": 40, - "wires": [ - [ - "544d20f02215011a" - ] - ] - }, - { - "id": "eb1a2387a1eeea76", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "enable LED", - "mode": "link", - "links": [ - "592ec13d8f8923a9", - "5baf89a2682265f7" - ], - "x": 385, - "y": 140, - "wires": [] - }, - { - "id": "0d8c6bc7887fb3c2", - "type": "ui_template", - "z": "e6f4d02efb300ea9", - "group": "365a30d0dfa83e95", - "name": "shutdown+background", - "order": 14, - "width": 7, - "height": 1, - "format": "\n", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "global", - "className": "", - "x": 580, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "ac242724fe7605a6", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "rescue incomplete project", - "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", - "outputs": 1, - "x": 310, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "4468f691.103eb8", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 1, - "width": 3, - "height": 2, - "passthru": false, - "label": "SCAN", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "1", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 540, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "6560dd25.9e76c4", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 3, - "width": 3, - "height": 2, - "passthru": false, - "label": "Settings", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "3", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 100, - "y": 620, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "62cd5288.2805fc", - "type": "ui_ui_control", - "z": "e6f4d02efb300ea9", - "name": "", - "events": "all", - "x": 280, - "y": 540, - "wires": [ - [] - ] - }, - { - "id": "71e72293.91c6fc", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 2, - "width": 3, - "height": 2, - "passthru": false, - "label": "Files", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "2", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 580, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "e7306ef2.3b4df", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 4, - "width": 3, - "height": 2, - "passthru": false, - "label": "Update&Info", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "4", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 110, - "y": 660, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "8955d11554f55e63", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "5b3e5aca21140e9a", - "order": 1, - "width": 6, - "height": 3, - "passthru": false, - "label": "Install Updates", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "date", - "topic": "", - "topicType": "str", - "x": 120, - "y": 720, - "wires": [ - [ - "1e7457ea9c2c5e09" - ] - ] - }, - { - "id": "1e7457ea9c2c5e09", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "update", - "mode": "link", - "links": [ - "39a502b38837273d" - ], - "x": 245, - "y": 720, - "wires": [] - }, - { - "id": "245e4341d4fb611c", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "pinmap_v2", - "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 790, - "y": 540, - "wires": [ - [ - "627406f3611511dc" - ] - ] - }, - { - "id": "627406f3611511dc", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "write", - "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", - "outputs": 1, - "x": 930, - "y": 540, - "wires": [ - [ - "50eeb3e362f9027f" - ] - ] - }, - { - "id": "88b1bddde110298a", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "false", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": "0.1", - "topic": "", - "x": 650, - "y": 540, - "wires": [ - [ - "245e4341d4fb611c" - ] - ] - }, - { - "id": "50eeb3e362f9027f", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "started1s", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "b4c843620c251c43", - "3876d5cbd248592b", - "a4c81754c148b86f", - "2e9b29c70969cf01", - "2477f81cddc8fa31", - "29036b35dfd672c6", - "592ec13d8f8923a9", - "cb40b9341bd22a28", - "d1efcd5fa9d25785", - "da61581182b7299e", - "2afb6a45c73fa244" - ], - "x": 1015, - "y": 540, - "wires": [] - }, - { - "id": "4f3121f158f06a61", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "motor run", - "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", - "outputs": 1, - "x": 860, - "y": 580, - "wires": [ - [] - ] - }, - { - "id": "4a8a04b1e5dca8fe", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "run rotor till endstop", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 690, - "y": 580, - "wires": [ - [ - "4f3121f158f06a61" - ] - ] - }, - { - "id": "c8167775e3401fad", - "type": "ui_template", - "z": "e6f4d02efb300ea9", - "group": "729f9ea6e3513c9b", - "name": "infotext", - "order": 4, - "width": 0, - "height": 0, - "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 580, - "y": 260, - "wires": [ - [] - ] - }, - { - "id": "6a3d9acbe097a3d2", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 120, - "wires": [ - [ - "cb6ebdabaaf7d0da" - ] - ] - }, - { - "id": "7ef6f1b5c67201fe", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 120, - "wires": [ - [] - ] - }, - { - "id": "86f7d1b2d763f6e2", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 160, - "wires": [ - [ - "c8a3fde5206ce1ae" - ] - ] - }, - { - "id": "fd799c931139764d", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 240, - "wires": [ - [ - "87be854db758a9a6" - ] - ] - }, - { - "id": "d5140d455122c49a", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 280, - "wires": [ - [ - "9daea4bd57f7a00e" - ] - ] - }, - { - "id": "194f3590dd4f6e3d", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 240, - "wires": [ - [] - ] - }, - { - "id": "2de69452e829d780", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "58e565fea35cb667", - "type": "ui_text_input", - "z": "481edaf6db5a7a54", - "name": "", - "label": "", - "tooltip": "", - "group": "365a30d0dfa83e95", - "order": 3, - "width": 4, - "height": 1, - "passthru": true, - "mode": "text", - "delay": "0", - "topic": "", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 320, - "y": 80, - "wires": [ - [ - "734ac3bff2df6837" - ] - ] - }, - { - "id": "97170908e1f4ac55", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "msg", - "func": "msg.payload=\"default\"\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 80, - "wires": [ - [ - "58e565fea35cb667" - ] - ] - }, - { - "id": "734ac3bff2df6837", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 80, - "wires": [ - [] - ] - }, - { - "id": "1dffb799fdf10cbc", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine settings", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 55, - "y": 80, - "wires": [ - [ - "97170908e1f4ac55", - "6a3d9acbe097a3d2", - "86f7d1b2d763f6e2", - "fd799c931139764d", - "d5140d455122c49a" - ] - ] - }, - { - "id": "a0156eaac7dd35e5", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "shutter", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", - "outputs": 1, - "x": 510, - "y": 200, - "wires": [ - [] - ] - }, - { - "id": "c7f5808d753480d4", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "6", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 170, - "y": 200, - "wires": [ - [ - "11f41a6030578ef4" - ] - ] - }, - { - "id": "11f41a6030578ef4", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 310, - "y": 200, - "wires": [ - [ - "a0156eaac7dd35e5" - ] - ] - }, - { - "id": "855cbcadef1163c5", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "enable", - "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 840, - "wires": [ - [ - "d1b87196ae5373ed", - "41e6a4649b6afbfb", - "2fd24f8e8e9c08b7", - "85a268108250ba88" - ] - ] - }, - { - "id": "1a443e20a973d2f1", - "type": "change", - "z": "481edaf6db5a7a54", - "name": "flag_pw true", - "rules": [ - { - "t": "set", - "p": "flag_pw", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 630, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "d1b87196ae5373ed", - "type": "change", - "z": "481edaf6db5a7a54", - "name": "flag_pw false", - "rules": [ - { - "t": "set", - "p": "flag_pw", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 430, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "03d92601c62b79d4", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "4s/0.5", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "0.1", - "crontab": "", - "once": true, - "onceDelay": "4", - "topic": "Repeat", - "payload": "0.1", - "payloadType": "str", - "x": 100, - "y": 840, - "wires": [ - [ - "855cbcadef1163c5" - ] - ] - }, - { - "id": "41e6a4649b6afbfb", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/camera/picam2_take_photo')\n\nreturn msg\n", - "outputs": 1, - "x": 450, - "y": 800, - "wires": [ - [ - "1a443e20a973d2f1", - "296636b7467fc745" - ] - ] - }, - { - "id": "85a268108250ba88", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "90223f7ddc082321", - "name": "preview_arducam", - "order": 1, - "width": 7, - "height": 9, - "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 450, - "y": 840, - "wires": [ - [ - "417f653ca0dfdcfc", - "180476141c2a44ad" - ] - ] - }, - { - "id": "296636b7467fc745", - "type": "link out", - "z": "481edaf6db5a7a54", - "name": "link out 1", - "mode": "link", - "links": [ - "2c58a1a66c4a8c11" - ], - "x": 575, - "y": 800, - "wires": [] - }, - { - "id": "417f653ca0dfdcfc", - "type": "delay", - "z": "481edaf6db5a7a54", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "allowrate": false, - "outputs": 1, - "x": 640, - "y": 840, - "wires": [ - [ - "e864254b18c23dd1" - ] - ] - }, - { - "id": "e864254b18c23dd1", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "motorrun", - "func": "from OpenScan import motorrun, load_int, load_bool\n\nif 'payload' not in msg:\n return\nenable_endstop = load_bool('rotor_enable_endstop')\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'), enable_endstop)\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'), enable_endstop)\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'), enable_endstop)\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'), enable_endstop)", - "outputs": 1, - "x": 780, - "y": 840, - "wires": [ - [] - ] - }, - { - "id": "180476141c2a44ad", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "global", - "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 880, - "wires": [ - [ - "8cbdbfecbd12ef83" - ] - ] - }, - { - "id": "1fe18f3b0b52aabd", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "LED", - "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", - "outputs": 1, - "x": 870, - "y": 880, - "wires": [ - [] - ] - }, - { - "id": "2fd24f8e8e9c08b7", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "load advanced", - "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", - "outputs": 1, - "x": 440, - "y": 720, - "wires": [ - [ - "923be3b2b25224b4" - ] - ] - }, - { - "id": "923be3b2b25224b4", - "type": "ui_ui_control", - "z": "481edaf6db5a7a54", - "name": "change visibility", - "events": "all", - "x": 640, - "y": 720, - "wires": [ - [] - ] - }, - { - "id": "c8a3fde5206ce1ae", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "shutter", - "order": 4, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 310, - "y": 160, - "wires": [ - [ - "034ec9f59e50a361", - "a0156eaac7dd35e5" - ] - ] - }, - { - "id": "034ec9f59e50a361", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "rate", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 160, - "wires": [ - [] - ] - }, - { - "id": "87be854db758a9a6", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "Cropy", - "order": 7, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 310, - "y": 240, - "wires": [ - [ - "194f3590dd4f6e3d" - ] - ] - }, - { - "id": "9daea4bd57f7a00e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "Cropx", - "order": 6, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 310, - "y": 280, - "wires": [ - [ - "2de69452e829d780" - ] - ] - }, - { - "id": "cb6ebdabaaf7d0da", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "Photos", - "order": 5, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 320, - "y": 120, - "wires": [ - [ - "7ef6f1b5c67201fe" - ] - ] - }, - { - "id": "82ecd3cd971cb7ea", - "type": "ui_text", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "order": 2, - "width": 3, - "height": 1, - "name": "projectname", - "label": "Projectname", - "format": "", - "layout": "row-left", - "className": "", - "x": 530, - "y": 40, - "wires": [] - }, - { - "id": "ed2974731fb8a84e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "ac7409105cfecac6", - "name": "threshold", - "order": 5, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 320, - "y": 520, - "wires": [ - [ - "06e1e19835a9816e" - ] - ] - }, - { - "id": "8cbdbfecbd12ef83", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "led", - "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", - "outputs": 1, - "x": 750, - "y": 880, - "wires": [ - [ - "1fe18f3b0b52aabd" - ] - ] - }, - { - "id": "06e1e19835a9816e", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 520, - "wires": [ - [] - ] - }, - { - "id": "2d5b1eb4380ae5a8", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 520, - "wires": [ - [ - "ed2974731fb8a84e" - ] - ] - }, - { - "id": "7dd287f40385922f", - "type": "ui_button", - "z": "481edaf6db5a7a54", - "name": "start ", - "group": "365a30d0dfa83e95", - "order": 10, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-play", - "payload": "", - "payloadType": "date", - "topic": "enabled", - "topicType": "str", - "x": 130, - "y": 1040, - "wires": [ - [ - "33d94a04b96a2de0", - "6d15f717d5a11002", - "9a6b30a0175a8ecd" - ] - ] - }, - { - "id": "579f2211199fd6ab", - "type": "ui_button", - "z": "481edaf6db5a7a54", - "name": "stop", - "group": "365a30d0dfa83e95", - "order": 12, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-stop", - "payload": "numberofphotos", - "payloadType": "global", - "topic": "", - "topicType": "str", - "x": 490, - "y": 1100, - "wires": [ - [ - "1787f08ed7070ddd", - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "1787f08ed7070ddd", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "stop", - "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", - "outputs": 1, - "x": 630, - "y": 1100, - "wires": [ - [] - ] - }, - { - "id": "e9b13dfd9f8d3711", - "type": "link out", - "z": "481edaf6db5a7a54", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "c8b93b42c720b9cf" - ], - "x": 395, - "y": 1000, - "wires": [] - }, - { - "id": "9654deebb668e012", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "1", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 290, - "y": 1140, - "wires": [ - [ - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "8367cfa0bf5bc5df", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine", - "links": [ - "200d4b9951b6e066", - "8689e938.dd9e38", - "e9b13dfd9f8d3711", - "f20f2dbc.0f123", - "fb13752beddee9f2" - ], - "x": 45, - "y": 1040, - "wires": [ - [ - "7dd287f40385922f" - ] - ] - }, - { - "id": "fb13752beddee9f2", - "type": "link out", - "z": "481edaf6db5a7a54", - "name": "", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "c8b93b42c720b9cf" - ], - "x": 525, - "y": 1040, - "wires": [] - }, - { - "id": "33d94a04b96a2de0", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "enable", - "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1100, - "wires": [ - [ - "579f2211199fd6ab" - ] - ] - }, - { - "id": "c1c044f3c2139f68", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 490, - "y": 1140, - "wires": [ - [ - "579f2211199fd6ab" - ] - ] - }, - { - "id": "1daf9e3a5bd5ab48", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "msg", - "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 430, - "y": 1040, - "wires": [ - [ - "fb13752beddee9f2" - ] - ] - }, - { - "id": "6d15f717d5a11002", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "disable", - "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 1000, - "wires": [ - [ - "e9b13dfd9f8d3711" - ] - ] - }, - { - "id": "9a6b30a0175a8ecd", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nenable_endstop = load_bool('rotor_enable_endstop')\n\nif enable_endstop:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nautofocus = load_bool('cam_autofocus') ##change##\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\n##change##\nif focus_min == focus_max or autofocus:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef focus(f):\n ##change##\n if autofocus:\n camera('/camera/picam2_af')\n else:\n camera('/camera/picam2_focus?focus=' + str(f))\n ##change##\n\ndef photo(counter2):\n camera('/camera/picam2_take_photo')\n ##change##\n focus(focuslist[returning[0]])\n if returning[0] < len(focuslist) - 1:\n returning[0] += 1\n else:\n returning[0] = 0\n ##change##\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\n\ndef stack_photo(i):\n\n camera('/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n\ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n ##change##\n focus(focuslist[i+1])\n else:\n camera(focuslist[0])\n sleep(1.7)\n\ndef photo_stack():\n camera(focuslist[0])\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n\n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n\n focus_thread.start()\n photo_thread.start()\n\n focus_thread.join()\n photo_thread.join()\n\n\ndef move_motor(enable_endstop=False):\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle,enable_endstop)\n motorrun('rotor',rotor_angle,enable_endstop)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor(enable_endstop)\n sleep(load_float(\"cam_delay_before\"))\n\n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", - "outputs": 1, - "x": 300, - "y": 1040, - "wires": [ - [ - "1daf9e3a5bd5ab48", - "795c85ad4f109567" - ] - ] - }, - { - "id": "afe47a9eaae6f67f", - "type": "ui_text", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "order": 1, - "width": 7, - "height": 1, - "name": "", - "label": "Current Status:", - "format": " {{msg.payload}} ", - "layout": "row-spread", - "className": "", - "x": 340, - "y": 40, - "wires": [] - }, - { - "id": "8608517d0567d63f", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadS", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 40, - "wires": [ - [ - "afe47a9eaae6f67f" - ] - ] - }, - { - "id": "6bf8344af427a6ba", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start status", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 55, - "y": 40, - "wires": [ - [ - "8608517d0567d63f" - ] - ] - }, - { - "id": "78cfe60013a1bea4", - "type": "ui_switch", - "z": "481edaf6db5a7a54", - "name": "", - "label": "Show Sharpness", - "tooltip": "", - "group": "ac7409105cfecac6", - "order": 2, - "width": 7, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 350, - "y": 380, - "wires": [ - [ - "9774e7ad3b506354" - ] - ] - }, - { - "id": "9774e7ad3b506354", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "focus", - "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", - "outputs": 1, - "x": 510, - "y": 380, - "wires": [ - [ - "f0af909f3e739b22" - ] - ] - }, - { - "id": "39c744466a21735e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "90223f7ddc082321", - "name": "focus_min", - "order": 3, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 990, - "y": 40, - "wires": [ - [ - "fa181d22775c2ce6" - ] - ] - }, - { - "id": "61aab497fa50898e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "90223f7ddc082321", - "name": "focus_max", - "order": 4, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 990, - "y": 80, - "wires": [ - [ - "c615034ea6b26174" - ] - ] - }, - { - "id": "5e83b653850fa16e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "ac7409105cfecac6", - "name": "stacksize", - "order": 4, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 320, - "y": 480, - "wires": [ - [ - "237c2135cdad86ea" - ] - ] - }, - { - "id": "dd7fb8791d34c751", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "enable", - "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 880, - "wires": [ - [ - "180476141c2a44ad" - ] - ] - }, - { - "id": "5baf89a2682265f7", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "enable led", - "links": [ - "eb1a2387a1eeea76" - ], - "x": 145, - "y": 880, - "wires": [ - [ - "dd7fb8791d34c751" - ] - ] - }, - { - "id": "6a26e8a7253d708c", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 830, - "y": 40, - "wires": [ - [ - "39c744466a21735e" - ] - ] - }, - { - "id": "35ad7e55833836c1", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 830, - "y": 80, - "wires": [ - [ - "61aab497fa50898e" - ] - ] - }, - { - "id": "9fd259de91de1da1", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine settings", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 735, - "y": 40, - "wires": [ - [ - "6a26e8a7253d708c", - "35ad7e55833836c1" - ] - ] - }, - { - "id": "fa181d22775c2ce6", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "rate", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1150, - "y": 40, - "wires": [ - [ - "ae5ee8787145906d" - ] - ] - }, - { - "id": "c615034ea6b26174", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "rate", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1150, - "y": 80, - "wires": [ - [ - "ae5ee8787145906d" - ] - ] - }, - { - "id": "ae5ee8787145906d", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "focus", - "func": "from OpenScan import camera\ncamera('/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", - "outputs": 1, - "x": 1290, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "f0af909f3e739b22", - "type": "ui_switch", - "z": "481edaf6db5a7a54", - "name": "", - "label": "Show Features", - "tooltip": "", - "group": "ac7409105cfecac6", - "order": 1, - "width": 7, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 340, - "y": 420, - "wires": [ - [ - "710fc2dbb5ef0167" - ] - ] - }, - { - "id": "710fc2dbb5ef0167", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "focus", - "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", - "outputs": 1, - "x": 510, - "y": 420, - "wires": [ - [ - "78cfe60013a1bea4" - ] - ] - }, - { - "id": "d93c2b67bcf23b9a", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 480, - "wires": [ - [ - "5e83b653850fa16e" - ] - ] - }, - { - "id": "237c2135cdad86ea", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 480, - "wires": [ - [] - ] - }, - { - "id": "fd0258418489839d", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine settings", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 95, - "y": 480, - "wires": [ - [ - "2d5b1eb4380ae5a8", - "d93c2b67bcf23b9a" - ] - ] - }, - { - "id": "c6f281351e11b58a", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "", - "props": [ - { - "p": "enabled", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 190, - "y": 600, - "wires": [ - [ - "ed2974731fb8a84e" - ] - ] - }, - { - "id": "ca4ca7fae36d312d", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "", - "props": [ - { - "p": "enabled", - "v": "false", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 190, - "y": 640, - "wires": [ - [ - "ed2974731fb8a84e" - ] - ] - }, - { - "id": "c8b93b42c720b9cf", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "sharpness/features", - "links": [ - "200d4b9951b6e066", - "e9b13dfd9f8d3711", - "fb13752beddee9f2" - ], - "x": 85, - "y": 380, - "wires": [ - [ - "78cfe60013a1bea4" - ] - ] - }, - { - "id": "795c85ad4f109567", - "type": "debug", - "z": "481edaf6db5a7a54", - "name": "debug 5", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 620, - "y": 1000, - "wires": [] - }, - { - "id": "ea54fcc2.cfcc2", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "get dirs", - "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"
RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", - "outputs": 1, - "x": 480, - "y": 180, - "wires": [ - [ - "f3662f8c7d3d7a2d", - "01e4783e148c6698" - ] - ] - }, - { - "id": "2f4c0f98.dee2", - "type": "link in", - "z": "80a3942785a26c29", - "name": "filelist", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc", - "a4f09e25.02569", - "ed35109311335099", - "fb13752beddee9f2" - ], - "x": 355, - "y": 220, - "wires": [ - [ - "ea54fcc2.cfcc2" - ] - ] - }, - { - "id": "952ce286.4ffd4", - "type": "ui_text", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "order": 4, - "width": 6, - "height": 1, - "name": "Status", - "label": "Status", - "format": "{{msg.status}}", - "layout": "row-spread", - "className": "", - "x": 250, - "y": 60, - "wires": [] - }, - { - "id": "d4383424.7807c8", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "upload", - "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", - "outputs": 2, - "x": 530, - "y": 460, - "wires": [ - [ - "9a132ab1.b21658" - ], - [ - "3d16b3789632784d", - "9a132ab1.b21658" - ] - ] - }, - { - "id": "50710948.71c308", - "type": "change", - "z": "80a3942785a26c29", - "name": "set", - "rules": [ - { - "t": "set", - "p": "set", - "pt": "global", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 750, - "y": 180, - "wires": [ - [ - "ada1b6f7cccc9344", - "85839a17fb7b58b9" - ] - ] - }, - { - "id": "834046a4.647938", - "type": "ui_text", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "order": 5, - "width": 6, - "height": 1, - "name": "Set", - "label": "Set:", - "format": "{{msg.payload.Name}}", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 220, - "wires": [] - }, - { - "id": "9a132ab1.b21658", - "type": "change", - "z": "80a3942785a26c29", - "name": "flag.true", - "rules": [ - { - "t": "set", - "p": "flag", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 780, - "y": 460, - "wires": [ - [ - "8689e938.dd9e38" - ] - ] - }, - { - "id": "3c67e97b.9d19a6", - "type": "function", - "z": "80a3942785a26c29", - "name": "enable", - "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 130, - "y": 340, - "wires": [ - [ - "7a93d1e18254685c", - "e434ef42bd6b92e8", - "d5d840183025d91b", - "ab9e90ab5a53a0dd", - "478994f671a3907d" - ] - ] - }, - { - "id": "bfc01f26.c32cf", - "type": "change", - "z": "80a3942785a26c29", - "name": "flag.false", - "rules": [ - { - "t": "set", - "p": "flag", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 420, - "y": 500, - "wires": [ - [ - "f20f2dbc.0f123" - ] - ] - }, - { - "id": "b33d604c.5f1a6", - "type": "link in", - "z": "80a3942785a26c29", - "name": "enable cloud", - "links": [ - "4082b136.dae18", - "8689e938.dd9e38", - "bd75f33b8a57c522", - "e9b13dfd9f8d3711", - "f20f2dbc.0f123", - "fb13752beddee9f2" - ], - "x": 35, - "y": 340, - "wires": [ - [ - "3c67e97b.9d19a6" - ] - ] - }, - { - "id": "f6bd1a04.470838", - "type": "change", - "z": "80a3942785a26c29", - "name": "set", - "rules": [ - { - "t": "set", - "p": "payload", - "pt": "msg", - "to": "set", - "tot": "global" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 410, - "y": 460, - "wires": [ - [ - "d4383424.7807c8" - ] - ] - }, - { - "id": "4082b136.dae18", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "b33d604c.5f1a6", - "87574a42938afec4" - ], - "x": 715, - "y": 140, - "wires": [] - }, - { - "id": "f20f2dbc.0f123", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "149e2e46b9623a2d" - ], - "x": 515, - "y": 500, - "wires": [] - }, - { - "id": "8689e938.dd9e38", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "149e2e46b9623a2d" - ], - "x": 875, - "y": 460, - "wires": [] - }, - { - "id": "15de0ebb.616d61", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 380, - "wires": [ - [ - "a7d89487.ee8858" - ] - ] - }, - { - "id": "a7d89487.ee8858", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", - "outputs": 1, - "x": 690, - "y": 380, - "wires": [ - [ - "a4f09e25.02569" - ] - ] - }, - { - "id": "a4f09e25.02569", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "2f4c0f98.dee2" - ], - "x": 775, - "y": 360, - "wires": [] - }, - { - "id": "7a93d1e18254685c", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "809c9427e14e2448", - "92c98e6ce7cd25f9" - ], - "x": 235, - "y": 500, - "wires": [] - }, - { - "id": "4d99c601c9881680", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "refresh", - "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", - "outputs": 2, - "x": 320, - "y": 180, - "wires": [ - [ - "ea54fcc2.cfcc2", - "b42e061fb1f1f3d7" - ], - [ - "6434e713f088012b" - ] - ] - }, - { - "id": "372e95797a3f2f3b", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "limit :)", - "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", - "outputs": 1, - "x": 90, - "y": 220, - "wires": [ - [ - "573edbfdb7500ddc" - ] - ] - }, - { - "id": "573edbfdb7500ddc", - "type": "delay", - "z": "80a3942785a26c29", - "name": "", - "pauseType": "rate", - "timeout": "5", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 230, - "y": 220, - "wires": [ - [ - "c46e10b9c201913e" - ] - ] - }, - { - "id": "dacb1f078b624e10", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 340, - "wires": [ - [ - "c8d65cc7c2ff7c36" - ] - ] - }, - { - "id": "92c98e6ce7cd25f9", - "type": "link in", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "7a93d1e18254685c", - "bd75f33b8a57c522" - ], - "x": 35, - "y": 180, - "wires": [ - [ - "c46e10b9c201913e" - ] - ] - }, - { - "id": "3d16b3789632784d", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "Terms", - "x": 770, - "y": 500, - "wires": [ - [] - ] - }, - { - "id": "6434e713f088012b", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "Terms", - "x": 470, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "c8d65cc7c2ff7c36", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", - "outputs": 1, - "x": 690, - "y": 340, - "wires": [ - [ - "a4f09e25.02569" - ] - ] - }, - { - "id": "f4e9a4bd79b4221f", - "type": "function", - "z": "80a3942785a26c29", - "name": "msg", - "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 340, - "wires": [ - [ - "dacb1f078b624e10" - ] - ] - }, - { - "id": "2806bf08ea21216d", - "type": "function", - "z": "80a3942785a26c29", - "name": "msg", - "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 380, - "wires": [ - [ - "15de0ebb.616d61" - ] - ] - }, - { - "id": "61990987acd0f263", - "type": "link in", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 45, - "y": 60, - "wires": [ - [ - "51579603bce21e98" - ] - ] - }, - { - "id": "e8e488a6dd5d0b33", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "Download", - "order": 8, - "width": 3, - "height": 1, - "format": "\n
Download\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 880, - "y": 260, - "wires": [ - [] - ] - }, - { - "id": "0c387c0291d6c131", - "type": "function", - "z": "80a3942785a26c29", - "name": "msg", - "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 750, - "y": 260, - "wires": [ - [ - "e8e488a6dd5d0b33" - ] - ] - }, - { - "id": "e5f38b4a07a5e278", - "type": "link in", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 655, - "y": 220, - "wires": [ - [ - "834046a4.647938" - ] - ] - }, - { - "id": "e434ef42bd6b92e8", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "upload2", - "order": 7, - "width": 3, - "height": 1, - "format": "upload", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 280, - "y": 460, - "wires": [ - [ - "f6bd1a04.470838" - ] - ] - }, - { - "id": "c46e10b9c201913e", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "refresh", - "order": 2, - "width": 4, - "height": 1, - "format": "refresh{{msg.text}}", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 160, - "y": 180, - "wires": [ - [ - "372e95797a3f2f3b", - "4d99c601c9881680" - ] - ] - }, - { - "id": "d5d840183025d91b", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "del set", - "order": 11, - "width": 2, - "height": 1, - "format": "delete set", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 270, - "y": 380, - "wires": [ - [ - "2806bf08ea21216d" - ] - ] - }, - { - "id": "ab9e90ab5a53a0dd", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "del ", - "order": 10, - "width": 2, - "height": 1, - "format": "delete all", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 270, - "y": 340, - "wires": [ - [ - "f4e9a4bd79b4221f" - ] - ] - }, - { - "id": "478994f671a3907d", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "combine", - "order": 9, - "width": 2, - "height": 1, - "format": "combine", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 280, - "y": 540, - "wires": [ - [ - "51bfd0fb7b1d292e" - ] - ] - }, - { - "id": "189c1eed09624a7b", - "type": "function", - "z": "80a3942785a26c29", - "name": "combine", - "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 580, - "wires": [ - [ - "1493398979a63775" - ] - ] - }, - { - "id": "51bfd0fb7b1d292e", - "type": "function", - "z": "80a3942785a26c29", - "name": "combine", - "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 420, - "y": 540, - "wires": [ - [] - ] - }, - { - "id": "da325be8e74179be", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "combine", - "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", - "outputs": 1, - "x": 560, - "y": 580, - "wires": [ - [ - "ed35109311335099" - ] - ] - }, - { - "id": "ed35109311335099", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "809c9427e14e2448", - "2f4c0f98.dee2" - ], - "x": 655, - "y": 580, - "wires": [] - }, - { - "id": "1493398979a63775", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "Combine", - "x": 420, - "y": 580, - "wires": [ - [ - "da325be8e74179be" - ] - ] - }, - { - "id": "ada1b6f7cccc9344", - "type": "link out", - "z": "80a3942785a26c29", - "name": "combine", - "mode": "link", - "links": [ - "6dd356510c446cf4" - ], - "x": 835, - "y": 180, - "wires": [] - }, - { - "id": "6dd356510c446cf4", - "type": "link in", - "z": "80a3942785a26c29", - "name": "combine", - "links": [ - "ada1b6f7cccc9344" - ], - "x": 175, - "y": 580, - "wires": [ - [ - "189c1eed09624a7b" - ] - ] - }, - { - "id": "b42e061fb1f1f3d7", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "397ab7f44b893c89", - "3876d5cbd248592b" - ], - "x": 435, - "y": 140, - "wires": [] - }, - { - "id": "b99505440832439f", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "diskspace", - "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", - "outputs": 1, - "x": 800, - "y": 100, - "wires": [ - [ - "92047434f8e9f927" - ] - ] - }, - { - "id": "92047434f8e9f927", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 950, - "y": 100, - "wires": [ - [] - ] - }, - { - "id": "f3662f8c7d3d7a2d", - "type": "delay", - "z": "80a3942785a26c29", - "name": "", - "pauseType": "rate", - "timeout": "5", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "minute", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "allowrate": false, - "outputs": 1, - "x": 650, - "y": 100, - "wires": [ - [ - "b99505440832439f" - ] - ] - }, - { - "id": "51579603bce21e98", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "read", - "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", - "outputs": 1, - "x": 130, - "y": 60, - "wires": [ - [ - "952ce286.4ffd4" - ] - ] - }, - { - "id": "9a5baae623355f9d", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "preview", - "order": 6, - "width": 6, - "height": 6, - "format": "
\n\n\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 1020, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "85839a17fb7b58b9", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "preview", - "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", - "outputs": 1, - "x": 880, - "y": 220, - "wires": [ - [ - "9a5baae623355f9d" - ] - ] - }, - { - "id": "01e4783e148c6698", - "type": "ui_table", - "z": "80a3942785a26c29", - "group": "b5fdd57b.15eda8", - "name": "", - "order": 1, - "width": 13, - "height": 7, - "columns": [ - { - "field": "Date", - "title": "Date", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Name", - "title": "Name", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Photos", - "title": "Photos", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Duration", - "title": "ΔT", - "width": "60", - "align": "left", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Size", - "title": "Size", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Status", - "title": "Status", - "width": "140", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - } - ], - "outputs": 1, - "cts": true, - "x": 610, - "y": 180, - "wires": [ - [ - "4082b136.dae18", - "50710948.71c308", - "834046a4.647938", - "0c387c0291d6c131" - ] - ] - }, - { - "id": "cb3437ec113e1b6f", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "", - "label": "SSH", - "tooltip": "", - "group": "4390b2ebcbbe104c", - "order": 3, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 390, - "y": 360, - "wires": [ - [ - "c24f61b87e3226dd" - ] - ] - }, - { - "id": "60fd0adce1cfeb82", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "", - "label": "Samba", - "tooltip": "", - "group": "4390b2ebcbbe104c", - "order": 4, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "test2", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 400, - "y": 400, - "wires": [ - [ - "441d3ef525e901da" - ] - ] - }, - { - "id": "c24f61b87e3226dd", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "ssh", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", - "outputs": 1, - "x": 530, - "y": 360, - "wires": [ - [] - ] - }, - { - "id": "c013e836e759a085", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "", - "group": "4390b2ebcbbe104c", - "order": 2, - "width": 6, - "height": 1, - "passthru": false, - "label": "Terms Of Use", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 120, - "y": 320, - "wires": [ - [ - "b78346ca3ce70c68" - ] - ] - }, - { - "id": "f0d8dbcca76a1926", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Agree", - "cancel": "Disagree", - "raw": true, - "className": "", - "topic": "", - "name": "", - "x": 410, - "y": 320, - "wires": [ - [ - "e95b86cbac1b03b9" - ] - ] - }, - { - "id": "34374044c0030625", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "General", - "group": "4390b2ebcbbe104c", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 740, - "y": 220, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "b2b6bf23c9989133", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Pinout", - "group": "70d0be671bf03ca7", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 430, - "y": 220, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "441d3ef525e901da", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "smb", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", - "outputs": 1, - "x": 530, - "y": 400, - "wires": [ - [] - ] - }, - { - "id": "3256bab150113a48", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Motor", - "group": "7a3279eea439bcdd", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 430, - "y": 140, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "7a186669a17daa71", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "camera", - "group": "d324f0b852c2df0a", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 420, - "y": 180, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "edac7dd292e7e486", - "type": "comment", - "z": "e43a27722b508115", - "name": "General Settings", - "info": "", - "x": 120, - "y": 280, - "wires": [] - }, - { - "id": "161b52034e578ee2", - "type": "comment", - "z": "e43a27722b508115", - "name": "Network", - "info": "", - "x": 100, - "y": 720, - "wires": [] - }, - { - "id": "f6d6cc35679ede63", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "more sets", - "label": "Advanced Settings", - "tooltip": "", - "group": "4390b2ebcbbe104c", - "order": 5, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 400, - "y": 480, - "wires": [ - [ - "f06a7bcad524e9f9" - ] - ] - }, - { - "id": "29745a36fc157f3f", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "more sets", - "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", - "outputs": 2, - "x": 820, - "y": 480, - "wires": [ - [ - "8750ad979e9ea246" - ], - [ - "f6d6cc35679ede63" - ] - ] - }, - { - "id": "bf23328f9fb11b22", - "type": "ui_ui_control", - "z": "e43a27722b508115", - "name": "change visibility", - "events": "all", - "x": 600, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "b37be1d222bc70c9", - "type": "inject", - "z": "e43a27722b508115", - "name": "1s_repeater", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "1", - "crontab": "", - "once": true, - "onceDelay": "2", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 150, - "y": 60, - "wires": [ - [ - "89eedf29b404f750" - ] - ] - }, - { - "id": "89eedf29b404f750", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "load advanced", - "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", - "outputs": 2, - "x": 360, - "y": 60, - "wires": [ - [ - "bf23328f9fb11b22" - ], - [ - "bf23328f9fb11b22" - ] - ] - }, - { - "id": "2050de5d9e02f69f", - "type": "comment", - "z": "e43a27722b508115", - "name": "Info Texts", - "info": "", - "x": 100, - "y": 140, - "wires": [] - }, - { - "id": "ded3086945a6d4b5", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "check ip address", - "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", - "outputs": 1, - "x": 250, - "y": 940, - "wires": [ - [ - "3cfe464506f46ecd" - ] - ] - }, - { - "id": "3cfe464506f46ecd", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "8ab79a98e536e0d6", - "order": 1, - "width": 0, - "height": 0, - "name": "", - "label": "Your local IP:", - "format": "{{msg.ip}}", - "layout": "row-spread", - "className": "", - "x": 430, - "y": 940, - "wires": [] - }, - { - "id": "bd206ad109831e6a", - "type": "comment", - "z": "e43a27722b508115", - "name": "OpenScanCloud", - "info": "", - "x": 120, - "y": 1260, - "wires": [] - }, - { - "id": "b70a9a665c1e4d36", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Cloud-settings", - "group": "12b719cba49817c9", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 740, - "y": 260, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "c9f0566601a3e130", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "12b719cba49817c9", - "order": 4, - "width": 0, - "height": 0, - "name": "", - "label": "Max. Number of Photos:", - "format": "{{msg.limit_photos}}", - "layout": "row-spread", - "className": "", - "x": 410, - "y": 1400, - "wires": [] - }, - { - "id": "9bd86d27ea499a2a", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "12b719cba49817c9", - "order": 5, - "width": 0, - "height": 0, - "name": "", - "label": "Max. Filesize (GB):", - "format": "{{msg.limit_filesize}}", - "layout": "row-spread", - "className": "", - "x": 390, - "y": 1440, - "wires": [] - }, - { - "id": "2c37f7030810d234", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "12b719cba49817c9", - "order": 3, - "width": 0, - "height": 0, - "name": "", - "label": "Credit (GB):", - "format": "{{msg.credit}}", - "layout": "row-spread", - "className": "", - "x": 370, - "y": 1480, - "wires": [] - }, - { - "id": "f40286c18afd4501", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "save", - "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", - "outputs": 2, - "x": 750, - "y": 1340, - "wires": [ - [ - "455a5266017ea121", - "50f73cee213ec05c" - ], - [ - "264eece408043021" - ] - ] - }, - { - "id": "455a5266017ea121", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "topic": "", - "name": "", - "x": 890, - "y": 1300, - "wires": [ - [] - ] - }, - { - "id": "c368df68593bc2bf", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "Token", - "tooltip": "", - "group": "12b719cba49817c9", - "order": 2, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 350, - "y": 1360, - "wires": [ - [ - "18fd1afa768187b3" - ] - ] - }, - { - "id": "18fd1afa768187b3", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "Save?", - "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", - "outputs": 2, - "x": 470, - "y": 1360, - "wires": [ - [ - "418aea2ec65573a0" - ], - [ - "9792c89c5f4429f9" - ] - ] - }, - { - "id": "f90a98899b7a71d0", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "text", - "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", - "outputs": 1, - "x": 230, - "y": 1360, - "wires": [ - [ - "c368df68593bc2bf" - ] - ] - }, - { - "id": "b4c843620c251c43", - "type": "link in", - "z": "e43a27722b508115", - "name": "token", - "links": [ - "960912e90ba5b5bc", - "50f73cee213ec05c", - "9792c89c5f4429f9", - "50eeb3e362f9027f" - ], - "x": 75, - "y": 1360, - "wires": [ - [ - "f90a98899b7a71d0" - ] - ] - }, - { - "id": "418aea2ec65573a0", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 610, - "y": 1340, - "wires": [ - [ - "f40286c18afd4501" - ] - ] - }, - { - "id": "9792c89c5f4429f9", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "mode": "link", - "links": [ - "b4c843620c251c43" - ], - "x": 555, - "y": 1380, - "wires": [] - }, - { - "id": "264eece408043021", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "links": [ - "5d267acc10020091", - "3876d5cbd248592b" - ], - "x": 835, - "y": 1380, - "wires": [] - }, - { - "id": "3876d5cbd248592b", - "type": "link in", - "z": "e43a27722b508115", - "name": "OSCparameters", - "links": [ - "960912e90ba5b5bc", - "264eece408043021", - "b42e061fb1f1f3d7", - "50eeb3e362f9027f" - ], - "x": 75, - "y": 1400, - "wires": [ - [ - "5daca3ec47f8e7fc" - ] - ] - }, - { - "id": "50f73cee213ec05c", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "links": [ - "b4c843620c251c43", - "5d267acc10020091" - ], - "x": 835, - "y": 1340, - "wires": [] - }, - { - "id": "95578e54a9b61cba", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 250, - "y": 1540, - "wires": [ - [ - "d7a5693da7855da8" - ] - ] - }, - { - "id": "d7a5693da7855da8", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", - "outputs": 2, - "x": 390, - "y": 1540, - "wires": [ - [ - "2b02b97dd1614e52" - ], - [ - "183a629accb417b1" - ] - ] - }, - { - "id": "183a629accb417b1", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 530, - "y": 1580, - "wires": [ - [] - ] - }, - { - "id": "2b02b97dd1614e52", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 530, - "y": 1540, - "wires": [ - [ - "3e4c15d7b538f816" - ] - ] - }, - { - "id": "3bf622f344172721", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "SUBMIT", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 810, - "y": 1540, - "wires": [ - [ - "e431cb2b8d217cee" - ] - ] - }, - { - "id": "e431cb2b8d217cee", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", - "outputs": 1, - "x": 950, - "y": 1540, - "wires": [ - [ - "106874534890f229" - ] - ] - }, - { - "id": "a38d7fde5c73210f", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Register", - "group": "12b719cba49817c9", - "order": 6, - "width": 2, - "height": 1, - "passthru": false, - "label": "Register", - "tooltip": "testtesttest", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "Please enter your email address:", - "payloadType": "str", - "topic": "Requesting an OpenScanCloud Token", - "topicType": "str", - "x": 100, - "y": 1540, - "wires": [ - [ - "95578e54a9b61cba" - ] - ] - }, - { - "id": "106874534890f229", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1090, - "y": 1540, - "wires": [ - [] - ] - }, - { - "id": "5daca3ec47f8e7fc", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", - "outputs": 1, - "x": 230, - "y": 1400, - "wires": [ - [ - "c9f0566601a3e130", - "9bd86d27ea499a2a", - "2c37f7030810d234" - ] - ] - }, - { - "id": "f34de19d4cf810a9", - "type": "comment", - "z": "e43a27722b508115", - "name": "Motor", - "info": "", - "x": 90, - "y": 1740, - "wires": [] - }, - { - "id": "26c2b58e21f97475", - "type": "comment", - "z": "e43a27722b508115", - "name": "Camera", - "info": "", - "x": 90, - "y": 2500, - "wires": [] - }, - { - "id": "a8ec972bad47a9a8", - "type": "comment", - "z": "e43a27722b508115", - "name": "Pinout", - "info": "", - "x": 90, - "y": 2960, - "wires": [] - }, - { - "id": "b03e8b51187e88eb", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "Rotor_delay (ms)", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 16, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.01", - "max": "0.2", - "step": "0.005", - "className": "", - "x": 450, - "y": 2140, - "wires": [ - [ - "11fd3363416433f9" - ] - ] - }, - { - "id": "6aae9d4fddf08cc0", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt delay", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 30, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.01", - "max": "0.2", - "step": "0.005", - "className": "", - "x": 420, - "y": 2380, - "wires": [ - [ - "e50492d1e18f43c6" - ] - ] - }, - { - "id": "543e1690693acbeb", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_acc", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 18, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.1", - "max": "2", - "step": "0.1", - "className": "", - "x": 420, - "y": 2180, - "wires": [ - [ - "e8b24efb0f30288e" - ] - ] - }, - { - "id": "9a56c087d941f1da", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_accramp", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 20, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "100", - "max": "5000", - "step": "100", - "className": "", - "x": 440, - "y": 2220, - "wires": [ - [ - "29f576be9e292232" - ] - ] - }, - { - "id": "dfdebe10dbf0e198", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotor_stepsperrotation", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 14, - "width": 3, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 460, - "y": 2100, - "wires": [ - [ - "78e256083f59f66f" - ] - ] - }, - { - "id": "af8dfe78cbd0c301", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 19, - "width": 3, - "height": 1, - "name": "rotor Accramp", - "label": "Acceleration ramp", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 2180, - "wires": [] - }, - { - "id": "ee4b8908a5b83880", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 13, - "width": 3, - "height": 1, - "name": "rotor_Steps per Rotation", - "label": "Steps per Rotation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 810, - "y": 2220, - "wires": [] - }, - { - "id": "c4deaa38c1b0adbf", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 17, - "width": 3, - "height": 1, - "name": "rotor Acc", - "label": "Acceleration", - "format": "", - "layout": "row-left", - "className": "", - "x": 760, - "y": 2140, - "wires": [] - }, - { - "id": "baec873a95fff48a", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 15, - "width": 3, - "height": 1, - "name": "rotor_delay", - "label": "Delay", - "format": "", - "layout": "row-left", - "className": "", - "x": 770, - "y": 2100, - "wires": [] - }, - { - "id": "355e89ab4e5484e4", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 26, - "width": 6, - "height": 1, - "name": "tt", - "label": "TURNTABLE", - "format": "", - "layout": "row-center", - "className": "", - "x": 90, - "y": 2300, - "wires": [] - }, - { - "id": "10687d331a732790", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_acc", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 32, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.1", - "max": "2", - "step": "0.1", - "className": "", - "x": 410, - "y": 2420, - "wires": [ - [ - "af88b9da72917d62" - ] - ] - }, - { - "id": "721b9680a3fa460e", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_accramp", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 34, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "500", - "step": "1", - "className": "", - "x": 430, - "y": 2460, - "wires": [ - [ - "b1b4678827d3a6dd" - ] - ] - }, - { - "id": "c6642c7470d3820c", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "tt_stepsperrotation", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 28, - "width": 3, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 450, - "y": 2340, - "wires": [ - [ - "eef89545ec0f6aa8" - ] - ] - }, - { - "id": "18e5918748660109", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 33, - "width": 3, - "height": 1, - "name": "ttAccramp", - "label": "Acceleration ramp", - "format": "", - "layout": "row-left", - "className": "", - "x": 760, - "y": 2460, - "wires": [] - }, - { - "id": "8e805244dc1899e8", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 27, - "width": 3, - "height": 1, - "name": "tt_steps per Rotation", - "label": "Steps per Rotation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 800, - "y": 2340, - "wires": [] - }, - { - "id": "a09e5fbea861bfb1", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 31, - "width": 3, - "height": 1, - "name": "tt Acc", - "label": "Acceleration", - "format": "", - "layout": "row-left", - "className": "", - "x": 750, - "y": 2420, - "wires": [] - }, - { - "id": "7b06448b3b222011", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 29, - "width": 3, - "height": 1, - "name": "tt_delay", - "label": "Delay", - "format": "", - "layout": "row-left", - "className": "", - "x": 760, - "y": 2380, - "wires": [] - }, - { - "id": "0dfc86d90258f9bb", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_angle", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 22, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "180", - "step": "1", - "className": "", - "x": 430, - "y": 2260, - "wires": [ - [ - "c4b5a38c5c1df3d2" - ] - ] - }, - { - "id": "9319d7d4f34c6d22", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 21, - "width": 3, - "height": 1, - "name": "rotor_angle", - "label": "Manual angle", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2260, - "wires": [] - }, - { - "id": "1610895f430b9aca", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_angle", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 36, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "180", - "step": "1", - "className": "", - "x": 420, - "y": 2500, - "wires": [ - [ - "0f3367983bb8e159" - ] - ] - }, - { - "id": "96a9febc0928b6f0", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 35, - "width": 3, - "height": 1, - "name": "tt_angle", - "label": "Manual angle", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2500, - "wires": [] - }, - { - "id": "e2c5ea8c16a5ea32", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 2, - "width": 6, - "height": 1, - "name": "rotor", - "label": "ROTOR", - "format": "", - "layout": "row-center", - "className": "", - "x": 90, - "y": 1820, - "wires": [] - }, - { - "id": "277037c4716d85bf", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_dir", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 38, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "1", - "className": "", - "x": 410, - "y": 2540, - "wires": [ - [ - "c9d2e31514def4fc" - ] - ] - }, - { - "id": "1361134e9847f003", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_dir", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 24, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "1", - "className": "", - "x": 420, - "y": 2300, - "wires": [ - [ - "523717b0f218a5fd" - ] - ] - }, - { - "id": "6b0d58943ecb8bb2", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 37, - "width": 3, - "height": 1, - "name": "tt_dir", - "label": "Direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2540, - "wires": [] - }, - { - "id": "08f93dd2aeedb391", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 23, - "width": 3, - "height": 1, - "name": "rotor_dir", - "label": "Direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2300, - "wires": [] - }, - { - "id": "46b91bef44714366", - "type": "link in", - "z": "e43a27722b508115", - "name": "advanced settings", - "links": [ - "8750ad979e9ea246" - ], - "x": 95, - "y": 100, - "wires": [ - [ - "89eedf29b404f750" - ] - ] - }, - { - "id": "8750ad979e9ea246", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "mode": "link", - "links": [ - "46b91bef44714366" - ], - "x": 955, - "y": 480, - "wires": [] - }, - { - "id": "2522f888dc58972f", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_delay_before", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 7, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "0.02", - "className": "", - "x": 430, - "y": 2680, - "wires": [ - [ - "5c752757090c49d2" - ] - ] - }, - { - "id": "30e8df3d616512d8", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_gain", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 11, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "10", - "step": "0.1", - "className": "", - "x": 400, - "y": 2720, - "wires": [ - [ - "a1769f0277834f6d" - ] - ] - }, - { - "id": "d855d926df89d65b", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_contrast", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 13, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 420, - "y": 2840, - "wires": [ - [ - "1a8b0ba21b4f3005", - "654bc70a18820828" - ] - ] - }, - { - "id": "7617517dc8ba2859", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_saturation", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 15, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 420, - "y": 2880, - "wires": [ - [ - "dc8fc962ff7d594b", - "e64feb03a791ca33" - ] - ] - }, - { - "id": "cbaa23c34e10fae1", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_jpeg_q", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 3, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "100", - "step": "1", - "className": "", - "x": 410, - "y": 2920, - "wires": [ - [ - "00e7836ccb3c4d0c" - ] - ] - }, - { - "id": "bbe443b039a14e21", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 6, - "width": 3, - "height": 1, - "name": "delay_before", - "label": "Delay before", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2680, - "wires": [] - }, - { - "id": "d320ed3d701e6cc2", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 10, - "width": 3, - "height": 1, - "name": "gain", - "label": "Gain", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 2720, - "wires": [] - }, - { - "id": "f5834dd4646c8af9", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 12, - "width": 3, - "height": 1, - "name": "contrast", - "label": "Contrast", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2840, - "wires": [] - }, - { - "id": "ae9a4e19469813ef", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 14, - "width": 3, - "height": 1, - "name": "saturation", - "label": "Saturation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2880, - "wires": [] - }, - { - "id": "bd629d0d31233c8b", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 2, - "width": 3, - "height": 1, - "name": "jpegQ", - "label": "Jpeg Quality", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 2920, - "wires": [] - }, - { - "id": "e89f61dbe6a6cffe", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "ext", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 3, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3080, - "wires": [ - [ - "885bc559fafec5f2" - ] - ] - }, - { - "id": "ece38cb172a12d75", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 2, - "width": 4, - "height": 1, - "name": "ext", - "label": "External Camera", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3080, - "wires": [] - }, - { - "id": "70014da0b6ab6698", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "light1", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 5, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3120, - "wires": [ - [ - "f70321c96bf81360" - ] - ] - }, - { - "id": "29634ea5f6d666df", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 4, - "width": 4, - "height": 1, - "name": "light1", - "label": "Light 1", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3120, - "wires": [] - }, - { - "id": "2544963852c6881a", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "light2", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 7, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3160, - "wires": [ - [ - "95e1603bbd06a69d" - ] - ] - }, - { - "id": "27903533cd85a59e", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 6, - "width": 4, - "height": 1, - "name": "light2", - "label": "Light 2", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3160, - "wires": [] - }, - { - "id": "a1394401246eb735", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotordir", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 9, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3200, - "wires": [ - [ - "a8f92ea6bf394640" - ] - ] - }, - { - "id": "bc0aa4bacdfa94ea", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 8, - "width": 4, - "height": 1, - "name": "rotordir", - "label": "Rotor direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3200, - "wires": [] - }, - { - "id": "f15ca4518b5f223e", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotorstep", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 11, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3240, - "wires": [ - [ - "06397bb46b3bb541" - ] - ] - }, - { - "id": "0d2924b160e7e383", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 10, - "width": 4, - "height": 1, - "name": "rotorstep", - "label": "Rotor step", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3240, - "wires": [] - }, - { - "id": "49900bb9047dd965", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotoren", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 13, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3280, - "wires": [ - [ - "687dcdc1ede11700" - ] - ] - }, - { - "id": "a4d743ca73ee1622", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 12, - "width": 4, - "height": 1, - "name": "rotoren", - "label": "Rotor enable", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3280, - "wires": [] - }, - { - "id": "5a90224dc998b417", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "ttdir", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 15, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3320, - "wires": [ - [ - "e220740c0d38ccb0" - ] - ] - }, - { - "id": "67dc1b544c4ddf9f", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 14, - "width": 4, - "height": 1, - "name": "ttdir", - "label": "Turntable direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3320, - "wires": [] - }, - { - "id": "d2364ab09627fe94", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "ttstep", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 17, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3360, - "wires": [ - [ - "79d7e5a705ab813a" - ] - ] - }, - { - "id": "145b67ac40721ba6", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 16, - "width": 4, - "height": 1, - "name": "ttstep", - "label": "Turntable step", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3360, - "wires": [] - }, - { - "id": "eef25405472acfee", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "endstop1", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 19, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3400, - "wires": [ - [ - "12d20f2274bcc511" - ] - ] - }, - { - "id": "35eb252a41413531", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 18, - "width": 4, - "height": 1, - "name": "endstop1", - "label": "Endstop Rotor", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3400, - "wires": [] - }, - { - "id": "74e455136b5ca5dd", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "endstop2", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 21, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3440, - "wires": [ - [ - "a4a89668ce4c9f05" - ] - ] - }, - { - "id": "3a74f653800eb831", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 20, - "width": 4, - "height": 1, - "name": "endstop2", - "label": "Endstop Turntable", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3440, - "wires": [] - }, - { - "id": "5fcef1cb2e9e4788", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "confirm", - "x": 680, - "y": 480, - "wires": [ - [ - "29745a36fc157f3f" - ] - ] - }, - { - "id": "f06a7bcad524e9f9", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", - "outputs": 1, - "x": 530, - "y": 480, - "wires": [ - [ - "5fcef1cb2e9e4788" - ] - ] - }, - { - "id": "f455fb39039617ae", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_rotation", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 5, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "270", - "step": "90", - "className": "", - "x": 410, - "y": 2960, - "wires": [ - [ - "3019576de193d9d6" - ] - ] - }, - { - "id": "fdfbc900fe424eb9", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 4, - "width": 3, - "height": 1, - "name": "cam_rot", - "label": "Image Rotation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2960, - "wires": [] - }, - { - "id": "c3699d6b9664ccca", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2100, - "wires": [ - [ - "dfdebe10dbf0e198" - ] - ] - }, - { - "id": "78e256083f59f66f", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2100, - "wires": [ - [] - ] - }, - { - "id": "0f9141b401322374", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2220, - "wires": [ - [ - "9a56c087d941f1da" - ] - ] - }, - { - "id": "29f576be9e292232", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2220, - "wires": [ - [] - ] - }, - { - "id": "23e3099b34c4e475", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2260, - "wires": [ - [ - "0dfc86d90258f9bb" - ] - ] - }, - { - "id": "c4b5a38c5c1df3d2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2260, - "wires": [ - [] - ] - }, - { - "id": "79a14162ac805fac", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2300, - "wires": [ - [ - "1361134e9847f003" - ] - ] - }, - { - "id": "523717b0f218a5fd", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2300, - "wires": [ - [] - ] - }, - { - "id": "f5cf780f3fa8997e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2140, - "wires": [ - [ - "b03e8b51187e88eb" - ] - ] - }, - { - "id": "11fd3363416433f9", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2140, - "wires": [ - [] - ] - }, - { - "id": "02060b3f3b294563", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2180, - "wires": [ - [ - "543e1690693acbeb" - ] - ] - }, - { - "id": "e8b24efb0f30288e", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2180, - "wires": [ - [] - ] - }, - { - "id": "de1ad8b27b72a5ac", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", - "outputs": 1, - "noerr": 4, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2340, - "wires": [ - [ - "c6642c7470d3820c" - ] - ] - }, - { - "id": "ed4d587cb4feb064", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2460, - "wires": [ - [ - "721b9680a3fa460e" - ] - ] - }, - { - "id": "5b02160c33605ae7", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2500, - "wires": [ - [ - "1610895f430b9aca" - ] - ] - }, - { - "id": "304c135ec09801e3", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2540, - "wires": [ - [ - "277037c4716d85bf" - ] - ] - }, - { - "id": "a91dcbe0f9a2416a", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2380, - "wires": [ - [ - "6aae9d4fddf08cc0" - ] - ] - }, - { - "id": "6b2eb1cb95e573f9", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2420, - "wires": [ - [ - "10687d331a732790" - ] - ] - }, - { - "id": "eef89545ec0f6aa8", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2340, - "wires": [ - [] - ] - }, - { - "id": "b1b4678827d3a6dd", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2460, - "wires": [ - [] - ] - }, - { - "id": "0f3367983bb8e159", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2500, - "wires": [ - [] - ] - }, - { - "id": "c9d2e31514def4fc", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2540, - "wires": [ - [] - ] - }, - { - "id": "e50492d1e18f43c6", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2380, - "wires": [ - [] - ] - }, - { - "id": "af88b9da72917d62", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2420, - "wires": [ - [] - ] - }, - { - "id": "43fe948b3e7234e2", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2680, - "wires": [ - [ - "2522f888dc58972f" - ] - ] - }, - { - "id": "5c752757090c49d2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2680, - "wires": [ - [] - ] - }, - { - "id": "435681b3f7625a7e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2720, - "wires": [ - [ - "30e8df3d616512d8" - ] - ] - }, - { - "id": "a1769f0277834f6d", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2720, - "wires": [ - [] - ] - }, - { - "id": "1de07c7d285cbaf3", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2840, - "wires": [ - [ - "d855d926df89d65b" - ] - ] - }, - { - "id": "1a8b0ba21b4f3005", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2840, - "wires": [ - [] - ] - }, - { - "id": "ebc9e283468eda31", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2880, - "wires": [ - [ - "7617517dc8ba2859" - ] - ] - }, - { - "id": "dc8fc962ff7d594b", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2880, - "wires": [ - [] - ] - }, - { - "id": "60d641613527c736", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2920, - "wires": [ - [ - "cbaa23c34e10fae1" - ] - ] - }, - { - "id": "00e7836ccb3c4d0c", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2920, - "wires": [ - [] - ] - }, - { - "id": "7f24c0c34a88ba04", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2960, - "wires": [ - [ - "f455fb39039617ae" - ] - ] - }, - { - "id": "3019576de193d9d6", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2960, - "wires": [ - [] - ] - }, - { - "id": "77bb7dc529d63a7e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3080, - "wires": [ - [ - "e89f61dbe6a6cffe" - ] - ] - }, - { - "id": "885bc559fafec5f2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3080, - "wires": [ - [] - ] - }, - { - "id": "cc6dabe017a9c8a8", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3400, - "wires": [ - [ - "eef25405472acfee" - ] - ] - }, - { - "id": "12d20f2274bcc511", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3400, - "wires": [ - [] - ] - }, - { - "id": "dcb9fed8122759fd", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3120, - "wires": [ - [ - "70014da0b6ab6698" - ] - ] - }, - { - "id": "f70321c96bf81360", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3120, - "wires": [ - [] - ] - }, - { - "id": "013d2057c2347a62", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3160, - "wires": [ - [ - "2544963852c6881a" - ] - ] - }, - { - "id": "95e1603bbd06a69d", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3160, - "wires": [ - [] - ] - }, - { - "id": "f88bbf11d5aa9a14", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3200, - "wires": [ - [ - "a1394401246eb735" - ] - ] - }, - { - "id": "a8f92ea6bf394640", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3200, - "wires": [ - [] - ] - }, - { - "id": "301af70731e096e5", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3240, - "wires": [ - [ - "f15ca4518b5f223e" - ] - ] - }, - { - "id": "06397bb46b3bb541", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3240, - "wires": [ - [] - ] - }, - { - "id": "0456a9ec4c236c9e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3280, - "wires": [ - [ - "49900bb9047dd965" - ] - ] - }, - { - "id": "687dcdc1ede11700", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3280, - "wires": [ - [] - ] - }, - { - "id": "09d37ba08ec0f163", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3320, - "wires": [ - [ - "5a90224dc998b417" - ] - ] - }, - { - "id": "37d954a4cf7e87ea", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3360, - "wires": [ - [ - "d2364ab09627fe94" - ] - ] - }, - { - "id": "e220740c0d38ccb0", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3320, - "wires": [ - [] - ] - }, - { - "id": "79d7e5a705ab813a", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3360, - "wires": [ - [] - ] - }, - { - "id": "21dc963d967d9c99", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3440, - "wires": [ - [ - "74e455136b5ca5dd" - ] - ] - }, - { - "id": "a4a89668ce4c9f05", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3440, - "wires": [ - [] - ] - }, - { - "id": "22ef66b0e2058be2", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 360, - "wires": [ - [ - "cb3437ec113e1b6f" - ] - ] - }, - { - "id": "9ce01c8ba97932c1", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 400, - "wires": [ - [ - "60fd0adce1cfeb82" - ] - ] - }, - { - "id": "81356177176eebcf", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 480, - "wires": [ - [ - "f6d6cc35679ede63" - ] - ] - }, - { - "id": "b78346ca3ce70c68", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 320, - "wires": [ - [ - "f0d8dbcca76a1926" - ] - ] - }, - { - "id": "e95b86cbac1b03b9", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "3e4c15d7b538f816", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 670, - "y": 1540, - "wires": [ - [ - "3bf622f344172721" - ] - ] - }, - { - "id": "0f0871baf322b6d0", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1820, - "wires": [ - [ - "6ebd15c61a5ca891" - ] - ] - }, - { - "id": "f21a95a732fadae6", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 5, - "width": 3, - "height": 1, - "name": "rotor_anglemin", - "label": "Min Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1820, - "wires": [] - }, - { - "id": "acd10a4c99ee8063", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1820, - "wires": [ - [] - ] - }, - { - "id": "6ebd15c61a5ca891", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_anglemin", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 6, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", - "className": "", - "x": 440, - "y": 1820, - "wires": [ - [ - "acd10a4c99ee8063" - ] - ] - }, - { - "id": "3ad0f0f206e4a873", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_anglemax", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 8, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", - "className": "", - "x": 440, - "y": 1860, - "wires": [ - [ - "031d7697768d0e77" - ] - ] - }, - { - "id": "3b6d759ed5be647f", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_anglestart", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 4, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", - "className": "", - "x": 440, - "y": 1900, - "wires": [ - [ - "be1954dd71d2c94c" - ] - ] - }, - { - "id": "edb1c8fae8b65c82", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1860, - "wires": [ - [ - "3ad0f0f206e4a873" - ] - ] - }, - { - "id": "031d7697768d0e77", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1860, - "wires": [ - [] - ] - }, - { - "id": "462a8f3ca75fc3c8", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1900, - "wires": [ - [ - "3b6d759ed5be647f" - ] - ] - }, - { - "id": "be1954dd71d2c94c", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1900, - "wires": [ - [] - ] - }, - { - "id": "3d7379753d2eda25", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 7, - "width": 3, - "height": 1, - "name": "rotor_anglemax", - "label": "Max Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1860, - "wires": [] - }, - { - "id": "9cc86d1bcae3ab4e", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 3, - "width": 3, - "height": 1, - "name": "rotor_anglestart", - "label": "Start Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1900, - "wires": [] - }, - { - "id": "2e9b29c70969cf01", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 135, - "y": 360, - "wires": [ - [ - "22ef66b0e2058be2", - "9ce01c8ba97932c1", - "81356177176eebcf", - "d54b85891248ba88" - ] - ] - }, - { - "id": "592ec13d8f8923a9", - "type": "link in", - "z": "e43a27722b508115", - "name": "ip address", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc", - "eb1a2387a1eeea76", - "c994c779e4bad800" - ], - "x": 85, - "y": 940, - "wires": [ - [ - "ded3086945a6d4b5", - "6ea3cdab41f20f92" - ] - ] - }, - { - "id": "cb40b9341bd22a28", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 185, - "y": 1820, - "wires": [ - [ - "0f0871baf322b6d0", - "edb1c8fae8b65c82", - "462a8f3ca75fc3c8", - "c3699d6b9664ccca", - "f5cf780f3fa8997e", - "02060b3f3b294563", - "0f9141b401322374", - "23e3099b34c4e475", - "79a14162ac805fac", - "de1ad8b27b72a5ac", - "a91dcbe0f9a2416a", - "6b2eb1cb95e573f9", - "ed4d587cb4feb064", - "5b02160c33605ae7", - "304c135ec09801e3", - "f036424d79645761", - "b7db72b7f0599ebd", - "fe62e12d458db2d4" - ] - ] - }, - { - "id": "d1efcd5fa9d25785", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 155, - "y": 2540, - "wires": [ - [ - "43fe948b3e7234e2", - "435681b3f7625a7e", - "1de07c7d285cbaf3", - "ebc9e283468eda31", - "60d641613527c736", - "7f24c0c34a88ba04", - "6281b2e6e081104d" - ] - ] - }, - { - "id": "da61581182b7299e", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 135, - "y": 3000, - "wires": [ - [ - "77bb7dc529d63a7e", - "dcb9fed8122759fd", - "013d2057c2347a62", - "f88bbf11d5aa9a14", - "301af70731e096e5", - "0456a9ec4c236c9e", - "09d37ba08ec0f163", - "37d954a4cf7e87ea", - "cc6dabe017a9c8a8", - "21dc963d967d9c99" - ] - ] - }, - { - "id": "7e1c84ec516ad0a6", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Reset default", - "group": "4390b2ebcbbe104c", - "order": 6, - "width": 6, - "height": 1, - "passthru": false, - "label": "Restore default settings", - "tooltip": "", - "color": "red", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "This can not be undone!", - "payloadType": "str", - "topic": "Restore default settings?", - "topicType": "str", - "x": 110, - "y": 620, - "wires": [ - [ - "53e6681d7254d484" - ] - ] - }, - { - "id": "53e6681d7254d484", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 270, - "y": 620, - "wires": [ - [ - "c11e79cfa7bc10b7" - ] - ] - }, - { - "id": "c11e79cfa7bc10b7", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 620, - "wires": [ - [ - "307782d10c1acdaf" - ] - ] - }, - { - "id": "307782d10c1acdaf", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 505, - "y": 620, - "wires": [] - }, - { - "id": "5fff689f9f8bc1ca", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": true, - "className": "", - "topic": "", - "name": "Info", - "x": 1010, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "cca3300a8f0daf4d", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Update&Info", - "group": "ddbd496e.93a288", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 750, - "y": 180, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "654bc70a18820828", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_contrast?contrast=\" + str(msg['payload']))", - "outputs": 1, - "x": 660, - "y": 2800, - "wires": [ - [] - ] - }, - { - "id": "e64feb03a791ca33", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/camera/picam2_saturation?saturation=\" + str(msg['payload']))", - "outputs": 1, - "x": 660, - "y": 2760, - "wires": [ - [] - ] - }, - { - "id": "81bd4381cd029958", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_delay_after", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 9, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "0.02", - "className": "", - "x": 440, - "y": 2640, - "wires": [ - [ - "e612073aded01a8f" - ] - ] - }, - { - "id": "0d92559980944ae3", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 8, - "width": 3, - "height": 1, - "name": "delay_after", - "label": "Delay after", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2640, - "wires": [] - }, - { - "id": "6281b2e6e081104d", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2640, - "wires": [ - [ - "81bd4381cd029958" - ] - ] - }, - { - "id": "e612073aded01a8f", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2640, - "wires": [ - [] - ] - }, - { - "id": "e2411b49791840e0", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "reboot", - "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", - "outputs": 1, - "x": 270, - "y": 520, - "wires": [ - [] - ] - }, - { - "id": "01c882fcc51b349c", - "type": "link in", - "z": "e43a27722b508115", - "name": "reboot", - "links": [ - "16c76929f88df841", - "fe3a855fee9e28c6", - "09d4a9c756161e10" - ], - "x": 155, - "y": 520, - "wires": [ - [ - "e2411b49791840e0" - ] - ] - }, - { - "id": "e51dd5e5c0f050d6", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "SSID", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 4, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "ssid", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 210, - "y": 980, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "9959649037cb063b", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "Password", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "password", - "delay": "0", - "topic": "password", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 220, - "y": 1020, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "1d42cb9a63409283", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "Country Code 2", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 6, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "country", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 240, - "y": 1060, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "84ecaafd629c0f7a", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "", - "group": "8ab79a98e536e0d6", - "order": 7, - "width": 0, - "height": 0, - "passthru": false, - "label": "Connect to Wifi", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "connect", - "topicType": "str", - "x": 240, - "y": 1100, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "6ea3cdab41f20f92", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "8ab79a98e536e0d6", - "order": 2, - "width": 0, - "height": 0, - "name": "", - "label": "Hotspot Mode", - "format": "{{msg.mode}}", - "layout": "row-spread", - "className": "", - "x": 240, - "y": 900, - "wires": [] - }, - { - "id": "a7d233f984009e2e", - "type": "function", - "z": "e43a27722b508115", - "name": "function 1", - "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 440, - "y": 980, - "wires": [ - [ - "9b851aa999e86fd7", - "021dc780b478fee6", - "9ec0ad9fd3687e9f" - ] - ] - }, - { - "id": "65518f3d4e3095e5", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 1", - "links": [ - "200d4b9951b6e066" - ], - "x": 85, - "y": 980, - "wires": [ - [ - "e51dd5e5c0f050d6", - "9959649037cb063b", - "1d42cb9a63409283" - ] - ] - }, - { - "id": "9b851aa999e86fd7", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", - "outputs": 1, - "x": 670, - "y": 980, - "wires": [ - [ - "c994c779e4bad800", - "11b19e9c6a4ffd8d", - "36890eb99a2ca1cf" - ] - ] - }, - { - "id": "11b19e9c6a4ffd8d", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 870, - "y": 980, - "wires": [ - [] - ] - }, - { - "id": "021dc780b478fee6", - "type": "debug", - "z": "e43a27722b508115", - "name": "debug 3", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 640, - "y": 920, - "wires": [] - }, - { - "id": "c994c779e4bad800", - "type": "link out", - "z": "e43a27722b508115", - "name": "link out 2", - "mode": "link", - "links": [ - "592ec13d8f8923a9" - ], - "x": 815, - "y": 1020, - "wires": [] - }, - { - "id": "1eef47e0074545a9", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", - "outputs": 2, - "x": 670, - "y": 1100, - "wires": [ - [ - "c994c779e4bad800", - "36890eb99a2ca1cf" - ], - [] - ] - }, - { - "id": "434b04d8a65951ce", - "type": "inject", - "z": "e43a27722b508115", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 440, - "y": 1140, - "wires": [ - [ - "1eef47e0074545a9" - ] - ] - }, - { - "id": "9ec0ad9fd3687e9f", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "bottom right", - "displayTime": "5", - "highlight": "", - "sendall": true, - "outputs": 0, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "Adding new Wifi", - "name": "", - "x": 670, - "y": 1020, - "wires": [] - }, - { - "id": "36890eb99a2ca1cf", - "type": "debug", - "z": "e43a27722b508115", - "name": "debug 4", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 860, - "y": 940, - "wires": [] - }, - { - "id": "6b7245c3dcb694c8", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "endstop_angle", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 12, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "1", - "className": "", - "x": 440, - "y": 2020, - "wires": [ - [ - "85ad07b8f973bbe2" - ] - ] - }, - { - "id": "69516440e3997111", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 11, - "width": 3, - "height": 1, - "name": "endstop_angle", - "label": "Endstop angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 2020, - "wires": [] - }, - { - "id": "85ad07b8f973bbe2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2020, - "wires": [ - [] - ] - }, - { - "id": "f036424d79645761", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2020, - "wires": [ - [ - "6b7245c3dcb694c8" - ] - ] - }, - { - "id": "253feafa5a2f8b1d", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "rotor_enable_endstop", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 10, - "width": 3, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 460, - "y": 1940, - "wires": [ - [ - "1916dc3fd04f0664", - "6cb92b9b9f0d6954" - ] - ] - }, - { - "id": "b7db72b7f0599ebd", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1940, - "wires": [ - [ - "253feafa5a2f8b1d" - ] - ] - }, - { - "id": "1916dc3fd04f0664", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1940, - "wires": [ - [] - ] - }, - { - "id": "de409e57a0c4bf41", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 9, - "width": 3, - "height": 1, - "name": "rotor_enable_endstop", - "label": "Enable Endstop", - "format": "", - "layout": "row-left", - "className": "", - "x": 800, - "y": 1940, - "wires": [] - }, - { - "id": "6cb92b9b9f0d6954", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "msg.enabled = msg.payload\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 1980, - "wires": [ - [ - "69516440e3997111", - "f036424d79645761", - "fe62e12d458db2d4", - "aea4e51b20951560" - ] - ] - }, - { - "id": "d54b85891248ba88", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 440, - "wires": [ - [ - "eefed04c25e3e4d6" - ] - ] - }, - { - "id": "eefed04c25e3e4d6", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "", - "label": "Group Stack Photos", - "tooltip": "Group photos that are part of the same focus photoset", - "group": "d324f0b852c2df0a", - "order": 1, - "width": "6", - "height": "1", - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 440, - "y": 440, - "wires": [ - [ - "2aaf7c7f0f0c146f" - ] - ] - }, - { - "id": "2aaf7c7f0f0c146f", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "group_stack_photos", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", - "outputs": 1, - "x": 660, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "84a1d063a2a2b018", - "type": "comment", - "z": "e43a27722b508115", - "name": "Messaging", - "info": "", - "x": 100, - "y": 3500, - "wires": [] - }, - { - "id": "a12ead9ccf239c19", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3560, - "wires": [ - [ - "d0a1a4947a1137ca" - ] - ] - }, - { - "id": "9a4c3cbe89994626", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "telegram_enable", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", - "outputs": 1, - "x": 520, - "y": 3560, - "wires": [ - [] - ] - }, - { - "id": "d0a1a4947a1137ca", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "telegram_enable", - "label": "Enable Telegram", - "tooltip": "Enable telegram bot", - "group": "220493325bb79987", - "order": 1, - "width": "6", - "height": "1", - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 340, - "y": 3560, - "wires": [ - [ - "9a4c3cbe89994626" - ] - ] - }, - { - "id": "28eeaa3a8eb77679", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "telegram_api_token", - "label": "Telegram Api Token", - "tooltip": "telegram api token", - "group": "220493325bb79987", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "password", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 350, - "y": 3600, - "wires": [ - [ - "1c08a329bd2a669c" - ] - ] - }, - { - "id": "bf8e971a52cddab1", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3600, - "wires": [ - [ - "28eeaa3a8eb77679" - ] - ] - }, - { - "id": "1c08a329bd2a669c", - "type": "function", - "z": "e43a27722b508115", - "name": "telegram_api_token", - "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 3600, - "wires": [ - [] - ] - }, - { - "id": "a26c0482377667c9", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "telegram_client_id", - "label": "Telegram Client Id", - "tooltip": "The Id of the user or channel to send the message to", - "group": "220493325bb79987", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 350, - "y": 3640, - "wires": [ - [ - "b5aba11033c5f952" - ] - ] - }, - { - "id": "058743d0e5afb87b", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3640, - "wires": [ - [ - "a26c0482377667c9" - ] - ] - }, - { - "id": "b5aba11033c5f952", - "type": "function", - "z": "e43a27722b508115", - "name": "telegram_client_id", - "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 3640, - "wires": [ - [] - ] - }, - { - "id": "c59e7b205d80fe0a", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Messaging", - "group": "220493325bb79987", - "order": 1, - "width": 0, - "height": 0, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 770, - "y": 300, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "2afb6a45c73fa244", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 2", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3600, - "wires": [ - [ - "a12ead9ccf239c19", - "bf8e971a52cddab1", - "058743d0e5afb87b" - ] - ] - }, - { - "id": "69885a9ce218eb71", - "type": "comment", - "z": "e43a27722b508115", - "name": "Coloritos", - "info": "", - "x": 100, - "y": 3740, - "wires": [] - }, - { - "id": "dc1cde67c3022e6b", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3800, - "wires": [ - [ - "0dccca85770c7936" - ] - ] - }, - { - "id": "b63e8246ad14ad9d", - "type": "function", - "z": "e43a27722b508115", - "name": "interface-color", - "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 540, - "y": 3800, - "wires": [ - [] - ] - }, - { - "id": "b7044aa75196b521", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 3", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3800, - "wires": [ - [ - "dc1cde67c3022e6b" - ] - ] - }, - { - "id": "0dccca85770c7936", - "type": "ui_dropdown", - "z": "e43a27722b508115", - "name": "interface_color", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "15edc2ce885dddb3", - "order": 1, - "width": 0, - "height": 0, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Aburrido", - "value": "#097479", - "type": "str" - }, - { - "label": "Morado", - "value": "#790974", - "type": "str" - }, - { - "label": "Berenjena", - "value": "#79093c", - "type": "str" - }, - { - "label": "Azul", - "value": "#093c79 ", - "type": "str" - }, - { - "label": "Oliva", - "value": "#747909", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 360, - "y": 3800, - "wires": [ - [ - "b63e8246ad14ad9d" - ] - ] - }, - { - "id": "667950f6671bd1a0", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 840, - "wires": [ - [ - "b82a1cbefad51cd8" - ] - ] - }, - { - "id": "5f32d7e78e368454", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 840, - "wires": [ - [] - ] - }, - { - "id": "b82a1cbefad51cd8", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "hostname", - "label": "Hostname", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 1, - "width": 0, - "height": 0, - "passthru": true, - "mode": "text", - "delay": 300, - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 360, - "y": 840, - "wires": [ - [ - "5f32d7e78e368454" - ] - ] - }, - { - "id": "5fd155711e29b1b8", - "type": "comment", - "z": "e43a27722b508115", - "name": "Monitoring", - "info": "", - "x": 100, - "y": 3860, - "wires": [] - }, - { - "id": "815702499384f118", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3920, - "wires": [ - [ - "bfdbdae28bf42ed4" - ] - ] - }, - { - "id": "464c8495f86daaa7", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "datadog_enable", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", - "outputs": 1, - "x": 520, - "y": 3920, - "wires": [ - [] - ] - }, - { - "id": "bfdbdae28bf42ed4", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "datadog_enable", - "label": "Enable Datadog", - "tooltip": "Enable Datadog monitoring", - "group": "33aff36289823faa", - "order": 1, - "width": "6", - "height": "1", - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 340, - "y": 3920, - "wires": [ - [ - "464c8495f86daaa7" - ] - ] - }, - { - "id": "f93ce2d26953341f", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "datadog_api_token", - "label": "Datadog Api Token", - "tooltip": "Datadog Api Token", - "group": "33aff36289823faa", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "password", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 350, - "y": 3960, - "wires": [ - [ - "647641e79884eb87" - ] - ] - }, - { - "id": "ee668e39d213070b", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3960, - "wires": [ - [ - "f93ce2d26953341f" - ] - ] - }, - { - "id": "647641e79884eb87", - "type": "function", - "z": "e43a27722b508115", - "name": "datadog_api_token", - "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 3960, - "wires": [ - [] - ] - }, - { - "id": "ff2dea1ab9cb7776", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 4", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3960, - "wires": [ - [ - "815702499384f118", - "ee668e39d213070b" - ] - ] - }, - { - "id": "a1b81e7fe94ad4e5", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", - "outputs": 1, - "x": 530, - "y": 3740, - "wires": [ - [] - ] - }, - { - "id": "2f3a3c0e682ae862", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "restart_interface", - "group": "15edc2ce885dddb3", - "order": 1, - "width": 0, - "height": 0, - "passthru": false, - "label": "Restart Interface", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 340, - "y": 3740, - "wires": [ - [ - "a1b81e7fe94ad4e5" - ] - ] - }, - { - "id": "fe62e12d458db2d4", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = false;\n}\nelse{\n data = true;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2060, - "wires": [ - [ - "96e2b45a7102156b" - ] - ] - }, - { - "id": "96e2b45a7102156b", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "rotor_endstop_pushed", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 10, - "width": 3, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "false", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "true", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 460, - "y": 2060, - "wires": [ - [ - "df66caa5e0497e65" - ] - ] - }, - { - "id": "df66caa5e0497e65", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2060, - "wires": [ - [] - ] - }, - { - "id": "aea4e51b20951560", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 9, - "width": 3, - "height": 1, - "name": "rotor_endstop_pushed", - "label": "Reverse Endstop", - "format": "", - "layout": "row-left", - "className": "", - "x": 800, - "y": 2060, - "wires": [] - }, - { - "id": "4c7fa5b5b27b83a5", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", - "outputs": 1, - "x": 260, - "y": 140, - "wires": [ - [ - "e23c514008cad1a1" - ] - ] - }, - { - "id": "80175eb8dc6ad009", - "type": "inject", - "z": "a5557543ccff5889", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 100, - "y": 140, - "wires": [ - [ - "4c7fa5b5b27b83a5" - ] - ] - }, - { - "id": "d7362e6e0ec7bdaa", - "type": "inject", - "z": "a5557543ccff5889", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 90, - "y": 220, - "wires": [ - [ - "4ce127c61c3c5966", - "beacc3dc5398fa79" - ] - ] - }, - { - "id": "4ce127c61c3c5966", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "prepare image creation", - "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", - "outputs": 1, - "x": 290, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "beacc3dc5398fa79", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 195, - "y": 260, - "wires": [] - }, - { - "id": "e23c514008cad1a1", - "type": "debug", - "z": "a5557543ccff5889", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 480, - "y": 140, - "wires": [] - }, - { - "id": "b0629875a30ae1d7", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", - "outputs": 2, - "x": 390, - "y": 540, - "wires": [ - [ - "1bbe2d769f42c313" - ], - [ - "fefe45404bdb19c4" - ] - ] - }, - { - "id": "c7b6d05a62172432", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "ddbd496e.93a288", - "order": 3, - "width": 0, - "height": 0, - "name": "", - "label": "Status:", - "format": "{{msg.status}}", - "layout": "row-spread", - "className": "", - "x": 210, - "y": 400, - "wires": [] - }, - { - "id": "fefe45404bdb19c4", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", - "outputs": 1, - "x": 550, - "y": 560, - "wires": [ - [ - "1bbe2d769f42c313", - "ae92a328af306ebb" - ] - ] - }, - { - "id": "d0104e0163745993", - "type": "link in", - "z": "a5557543ccff5889", - "name": "", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 115, - "y": 440, - "wires": [ - [ - "ec30638407332e43", - "38cbf7965d1c1834", - "49f1ecb29a3f84f4" - ] - ] - }, - { - "id": "ec30638407332e43", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadS", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 480, - "wires": [ - [ - "2852023f3aa8db10" - ] - ] - }, - { - "id": "2852023f3aa8db10", - "type": "ui_dropdown", - "z": "a5557543ccff5889", - "name": "", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "ddbd496e.93a288", - "order": 5, - "width": 2, - "height": 1, - "passthru": false, - "multiple": false, - "options": [ - { - "label": "stable", - "value": "stable", - "type": "str" - }, - { - "label": "beta", - "value": "beta", - "type": "str" - }, - { - "label": "meanwhile", - "value": "meanwhile", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 340, - "y": 480, - "wires": [ - [ - "1e10b387ee30c486" - ] - ] - }, - { - "id": "1e10b387ee30c486", - "type": "function", - "z": "a5557543ccff5889", - "name": "write", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 480, - "wires": [ - [] - ] - }, - { - "id": "274129c51b0b87ef", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "ddbd496e.93a288", - "order": 4, - "width": 4, - "height": 1, - "name": "", - "label": "Updatetype: ", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 610, - "y": 480, - "wires": [] - }, - { - "id": "51cd8c8643e6b46a", - "type": "ui_switch", - "z": "a5557543ccff5889", - "name": "", - "label": "Auto-check update availability", - "tooltip": "", - "group": "ddbd496e.93a288", - "order": 6, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 410, - "y": 440, - "wires": [ - [ - "1ab4c6b4b232a022" - ] - ] - }, - { - "id": "38cbf7965d1c1834", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 440, - "wires": [ - [ - "51cd8c8643e6b46a" - ] - ] - }, - { - "id": "1ab4c6b4b232a022", - "type": "function", - "z": "a5557543ccff5889", - "name": "write", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 610, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "ae92a328af306ebb", - "type": "ui_toast", - "z": "a5557543ccff5889", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "NO", - "cancel": "YES", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 710, - "y": 560, - "wires": [ - [ - "2de63e8e3ae5fb0c", - "929281fef53e09f8" - ] - ] - }, - { - "id": "cbd0afc4aa7b302a", - "type": "link in", - "z": "a5557543ccff5889", - "name": "update status", - "links": [ - "1bbe2d769f42c313", - "42061b28cff81f99" - ], - "x": 115, - "y": 400, - "wires": [ - [ - "c7b6d05a62172432", - "c94623ddd9d95f78" - ] - ] - }, - { - "id": "1bbe2d769f42c313", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "cbd0afc4aa7b302a" - ], - "x": 665, - "y": 520, - "wires": [] - }, - { - "id": "7cf60615d93e696b", - "type": "ui_button", - "z": "a5557543ccff5889", - "name": "", - "group": "ddbd496e.93a288", - "order": 7, - "width": 6, - "height": 1, - "passthru": false, - "label": "Check Updates", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 180, - "y": 560, - "wires": [ - [ - "b0629875a30ae1d7" - ] - ] - }, - { - "id": "2de63e8e3ae5fb0c", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", - "outputs": 1, - "x": 880, - "y": 560, - "wires": [ - [ - "42061b28cff81f99", - "fe3a855fee9e28c6" - ] - ] - }, - { - "id": "929281fef53e09f8", - "type": "function", - "z": "a5557543ccff5889", - "name": "msg", - "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 850, - "y": 520, - "wires": [ - [ - "42061b28cff81f99" - ] - ] - }, - { - "id": "42061b28cff81f99", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "cbd0afc4aa7b302a" - ], - "x": 995, - "y": 520, - "wires": [] - }, - { - "id": "49f1ecb29a3f84f4", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 520, - "wires": [ - [ - "b0629875a30ae1d7" - ] - ] - }, - { - "id": "fe3a855fee9e28c6", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "9bb0adbd716ce347", - "01c882fcc51b349c" - ], - "x": 995, - "y": 560, - "wires": [] - }, - { - "id": "5e7d5e4335d37794", - "type": "link in", - "z": "a5557543ccff5889", - "name": "", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 95, - "y": 700, - "wires": [ - [ - "2bb5fe78e09fec8a" - ] - ] - }, - { - "id": "2bb5fe78e09fec8a", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "msg", - "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", - "outputs": 1, - "x": 210, - "y": 700, - "wires": [ - [ - "dbc77052ac950624", - "d97c3068ef5fef96", - "73a3b828f862312b", - "901e31453b2bdff8", - "f983854748ee4763", - "5347c7c517f5e8c7", - "3a5016f7003cd72c", - "6d720c4a4ecd9475", - "6438b7d060a70d81" - ] - ] - }, - { - "id": "d97c3068ef5fef96", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 2, - "width": 0, - "height": 0, - "name": "", - "label": "OS:", - "format": "{{msg.os}}", - "layout": "row-spread", - "className": "", - "x": 490, - "y": 740, - "wires": [] - }, - { - "id": "73a3b828f862312b", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 8, - "width": 0, - "height": 0, - "name": "", - "label": "Flask:", - "format": "{{msg.flask}}", - "layout": "row-spread", - "className": "", - "x": 490, - "y": 780, - "wires": [] - }, - { - "id": "dbc77052ac950624", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 1, - "width": 0, - "height": 0, - "name": "", - "label": "Device:", - "format": "{{msg.device}}", - "layout": "row-spread", - "className": "", - "x": 500, - "y": 700, - "wires": [] - }, - { - "id": "3f42560297fe6978", - "type": "ui_template", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "name": "Download LOG", - "order": 10, - "width": 6, - "height": 1, - "format": "\n
Download error log\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 180, - "y": 1060, - "wires": [ - [] - ] - }, - { - "id": "c94623ddd9d95f78", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "get update", - "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", - "outputs": 1, - "x": 210, - "y": 360, - "wires": [ - [] - ] - }, - { - "id": "39a502b38837273d", - "type": "link in", - "z": "a5557543ccff5889", - "name": "", - "links": [ - "1e7457ea9c2c5e09" - ], - "x": 245, - "y": 600, - "wires": [ - [ - "b0629875a30ae1d7" - ] - ] - }, - { - "id": "901e31453b2bdff8", - "type": "delay", - "z": "a5557543ccff5889", - "name": "", - "pauseType": "delay", - "timeout": "10", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 220, - "y": 740, - "wires": [ - [ - "2bb5fe78e09fec8a" - ] - ] - }, - { - "id": "f983854748ee4763", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 3, - "width": 0, - "height": 0, - "name": "", - "label": "", - "format": "{{msg.osdate}}", - "layout": "row-spread", - "className": "", - "x": 490, - "y": 820, - "wires": [] - }, - { - "id": "5347c7c517f5e8c7", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 4, - "width": 0, - "height": 0, - "name": "", - "label": "CPU temp:", - "format": "{{msg.temp}}", - "layout": "row-spread", - "className": "", - "x": 510, - "y": 860, - "wires": [] - }, - { - "id": "3a5016f7003cd72c", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 5, - "width": 0, - "height": 0, - "name": "", - "label": "CPU memory:", - "format": "{{msg.cpu}}", - "layout": "row-spread", - "className": "", - "x": 520, - "y": 900, - "wires": [] - }, - { - "id": "6d720c4a4ecd9475", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 6, - "width": 0, - "height": 0, - "name": "", - "label": "Swap memory:", - "format": "{{msg.swap}}", - "layout": "row-spread", - "className": "", - "x": 520, - "y": 940, - "wires": [] - }, - { - "id": "6438b7d060a70d81", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 7, - "width": 0, - "height": 0, - "name": "", - "label": "Diskspace:", - "format": "{{msg.diskspace}}", - "layout": "row-spread", - "className": "", - "x": 510, - "y": 980, - "wires": [] - }, - { - "id": "8d012912f302be85", - "type": "ui_button", - "z": "a5557543ccff5889", - "name": "", - "group": "ddbd496e.93a288", - "order": 8, - "width": 6, - "height": 1, - "passthru": false, - "label": "Show Details/Changelog", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 210, - "y": 640, - "wires": [ - [ - "5242607a723cc628" - ] - ] - }, - { - "id": "5242607a723cc628", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "Changelog", - "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", - "outputs": 1, - "x": 430, - "y": 640, - "wires": [ - [ - "573722197b15bf84" - ] - ] - }, - { - "id": "573722197b15bf84", - "type": "ui_toast", - "z": "a5557543ccff5889", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": true, - "className": "", - "topic": "", - "name": "", - "x": 610, - "y": 640, - "wires": [ - [] - ] - }, - { - "id": "cde61b7de9eeaba7", - "type": "ui_button", - "z": "a5557543ccff5889", - "name": "", - "group": "3ce32450.e0cffc", - "order": 9, - "width": 0, - "height": 0, - "passthru": false, - "label": "Expand Root", - "tooltip": "Sets the maximum space your SD card admits", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "expand", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 510, - "y": 1020, - "wires": [ - [ - "eab36487d201f867" - ] - ] - }, - { - "id": "eab36487d201f867", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "", - "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", - "outputs": 1, - "x": 690, - "y": 1020, - "wires": [ - [] - ] - } -] \ No newline at end of file diff --git a/update/2024-11S/meanwhile/routine.py b/update/2024-11S/meanwhile/routine.py index 0b822be..4f7aeca 100644 --- a/update/2024-11S/meanwhile/routine.py +++ b/update/2024-11S/meanwhile/routine.py @@ -1,3 +1,4 @@ +# The contents of this file are embedded in the OpenScan app (Node-RED) from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \ load_bool, camera from time import sleep, strftime, time @@ -9,22 +10,17 @@ import math import threading import numpy as np +import json if load_str("status_internal_cam") == "no camera found" or load_str("status_internal_cam")[:5] == "Featu": return -#motorrun('rotor', 140, ES_enable=True, ES_start_state=True) -#motorrun('rotor', 10) - - - save('status_internal_cam', 'Routine-preparing') -camera('/picam2_switch_mode?mode=1') +camera('/v1/camera/picam2_switch_mode?mode=1') save('cam_sharparea', False) save('cam_features', False) - projectname = load_str("routine_projectname") angle_max = load_int('rotor_anglemax') angle_min = load_int('rotor_anglemin') @@ -38,7 +34,6 @@ photocount = load_int('routine_photocount') -autofocus = load_bool('cam_autofocus') ##change## focus_min = load_float('cam_focus_min') focus_max = load_float('cam_focus_max') stacksize = load_int('cam_stacksize') @@ -49,8 +44,7 @@ telegram_api_token = load_str('telegram_api_token') telegram_client_id = load_str('telegram_client_id') -##change## -if focus_min == focus_max || autofocus: +if focus_min == focus_max: stacksize = 1 focuslist = [] @@ -93,61 +87,46 @@ def get_eta(starttime, photocounter, count): return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str( int(photocount / counter * (time() - starttime))) + 's' -def focus(f): - ##change## - if autofocus: - camera('/picam2_af') - else: - camera('/picam2_focus?focus=' + str(f)) - ##change## - def photo(counter2): - camera('/picam2_take_photo') - ##change## - focus(focuslist[returning[0]]) - if returning[0] < len(focuslist) - 1: - returning[0] += 1 - else: - returning[0] = 0 - ##change## + camera('/v1/camera/picam2_take_photo') + returning[0] = focus(returning[0]) zip.write(temppath, projectname + '_' + str(counter) + ".jpg") - def stack_photo(i): - - camera('/picam2_take_photo') + + camera('/v1/camera/picam2_take_photo') if group_stack_photos: name = projectname + '_' + str(counter) + "/" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg' else: name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg' zip.write(temppath, name) - + def stack_focus(i): sleep(load_float('cam_shutter')/1000000*2) if i < len(focuslist)-1: - ##change## - focus(focuslist[i+1]) + camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1])) else: - camera(focuslist[0]) + camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0])) sleep(1.7) def photo_stack(): - camera(focuslist[0]) + camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0])) for i in range(len(focuslist)): if load_str('status_internal_cam') == "Routine-stopping": break save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + "-F"+ str(i+1)) - + focus_thread = threading.Thread(target=stack_focus, args=(i,)) photo_thread = threading.Thread(target=stack_photo, args=(i,)) - + focus_thread.start() photo_thread.start() - + focus_thread.join() photo_thread.join() + def move_motor(): rotor_angle = position[0] - position_last[0] msg['payload2'].append(rotor_angle) @@ -183,6 +162,15 @@ def check_diskspace(): save('status_internal_cam', 'Routine-stopping') return +def focus(i): + f = focuslist[i] + camera('/v1/camera/picam2_focus?focus=' + str(f)) + if i < len(focuslist) - 1: + i += 1 + else: + i = 0 + return i + def send_telegram_message(message, telegram_api_token, telegram_client_id): telegram_bot_path = '/usr/local/bin/send-telegram' run([telegram_bot_path,"-a",telegram_api_token,"-c",telegram_client_id,"-m",message]) @@ -207,7 +195,7 @@ def send_telegram_message(message, telegram_api_token, telegram_client_id): move_motor() sleep(load_float("cam_delay_before")) - + if stacksize ==1: returning = [counter2] photo(returning) @@ -218,7 +206,19 @@ def send_telegram_message(message, telegram_api_token, telegram_client_id): sleep(load_float("cam_delay_after")) ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str( - int(photocount / counter * (time() - starttime))) + 's' + int(photocount / counter * (time() - starttime))) + 's' + + status = { + "projectname": projectname, + "total_photos": photocount, + "current_photo": counter, + "stacksize": steps, + "start_time": int(starttime), + + } + with open('/tmp/status.json', 'w') as status_file: + json.dump(status, status_file) + position_last = position zip.close() @@ -226,10 +226,21 @@ def send_telegram_message(message, telegram_api_token, telegram_client_id): send_telegram_message("[STOP] " + hostname + " stop " + projectname, telegram_api_token, telegram_client_id) except Exception as e: print(e) -camera('/picam2_switch_mode?mode=0') +camera('/v1/camera/picam2_switch_mode?mode=0') save('status_internal_cam', 'Routine-done') +# Delete the status.json file +import os + +try: + os.remove('/tmp/status.json') +except FileNotFoundError: + pass # File doesn't exist, so no need to delete +except Exception as e: + print(f"Error deleting /tmp/status.json: {e}") + + motorrun('rotor', -position_last[0] ) motorrun('tt', position_last[1]) diff --git a/update/2024-11S/meanwhile/start.sh b/update/2024-11S/meanwhile/start.sh deleted file mode 100755 index 223ffae..0000000 --- a/update/2024-11S/meanwhile/start.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -flow_dir="/home/pi/OpenScan/settings/.node-red" -flows_json_file=$flow_dir/flows.json -hostname=`hostname` -echo $hostname -session_token=`echo $RANDOM | md5sum | head -c 20` -echo $session_token > /home/pi/OpenScan/settings/session_token - -cat $flow_dir/flows.json.tmpl|sed "s|{{ hostname }}|$hostname.local|g" > $flows_json_file -sed -i "s|{{ session_token }}|$session_token|g" $flows_json_file diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index bd274b0..d4a0d47 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -58,22 +58,22 @@ "1": { "src": "meanwhile/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 13012 + "filesize": 17869 }, "2": { "src": "meanwhile/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10322 + "filesize": 10249 }, "3": { "src": "meanwhile/config.txt", "dst": "/boot/config.txt", - "filesize": 2185 + "filesize": 864 }, "4": { - "src": "meanwhile/flows.json.tmpl", - "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", - "filesize": 340275 + "src": "meanwhile/flows.json", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", + "filesize": 327504 }, "5": { "src": "meanwhile/settings.js", diff --git a/update/meanwhile/config.txt b/update/meanwhile/config.txt index cc525ae..6d55082 100755 --- a/update/meanwhile/config.txt +++ b/update/meanwhile/config.txt @@ -2,56 +2,8 @@ # http://rpf.io/configtxt # Some settings may impact device functionality. See link above for details -# uncomment if you get no picture on HDMI for a default "safe" mode -#hdmi_safe=1 - -# uncomment the following to adjust overscan. Use positive numbers if console -# goes off screen, and negative if there is too much border -#overscan_left=16 -#overscan_right=16 -#overscan_top=16 -#overscan_bottom=16 - -# uncomment to force a console size. By default it will be display's size minus -# overscan. -#framebuffer_width=1280 -#framebuffer_height=720 - -# uncomment if hdmi display is not detected and composite is being output -#hdmi_force_hotplug=1 - -# uncomment to force a specific HDMI mode (this will force VGA) -#hdmi_group=1 -#hdmi_mode=1 - -# uncomment to force a HDMI mode rather than DVI. This can make audio work in -# DMT (computer monitor) modes -#hdmi_drive=2 - -# uncomment to increase signal to HDMI, if you have interference, blanking, or -# no display -#config_hdmi_boost=4 - -# uncomment for composite PAL -#sdtv_mode=2 - -#uncomment to overclock the arm. 700 MHz is the default. -#arm_freq=800 - -# Uncomment some or all of these to enable the optional hardware interfaces -#dtparam=i2c_arm=on -#dtparam=i2s=on -#dtparam=spi=on - -# Uncomment this to enable infrared communication. -#dtoverlay=gpio-ir,gpio_pin=17 -#dtoverlay=gpio-ir-tx,gpio_pin=18 - # Additional overlays and parameters are documented /boot/overlays/README -# Enable audio (loads snd_bcm2835) -dtparam=audio=on - # Automatically load overlays for detected cameras camera_auto_detect=1 @@ -60,7 +12,7 @@ display_auto_detect=1 # Enable DRM VC4 V3D driver dtoverlay=vc4-kms-v3d -max_framebuffers=2 +max_framebuffers=1 # Disable compensation for displays with overscan disable_overscan=1 @@ -71,15 +23,12 @@ disable_overscan=1 # (e.g. for USB device mode) or if USB support is not required. otg_mode=1 -[all] - [pi4] # Run as fast as firmware / board allows arm_boost=1 +dtoverlay=imx519,cma-512 [all] camera_auto_detect=0 gpu_mem=256 -dtoverlay=vc4-fkms-v3d dtoverlay=imx519 -#dtoverlay=imx519,media-controller=1 From 56ec1127c335d91984b2891a149308615d351b8f Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 28 Sep 2024 01:07:23 +0200 Subject: [PATCH 12/38] add statistics file --- .../2024-11S/meanwhile/OpenScanStatistics.py | 18 ++ update/2024-11S/meanwhile/expand_root.sh | 7 + update/2024-11S/meanwhile/flows.json | 248 ++++++++++++++++-- update/2024-11S/meanwhile/startup.sh | 11 + update/2024-11S/update.json | 17 +- 5 files changed, 284 insertions(+), 17 deletions(-) create mode 100755 update/2024-11S/meanwhile/OpenScanStatistics.py create mode 100755 update/2024-11S/meanwhile/expand_root.sh create mode 100755 update/2024-11S/meanwhile/startup.sh diff --git a/update/2024-11S/meanwhile/OpenScanStatistics.py b/update/2024-11S/meanwhile/OpenScanStatistics.py new file mode 100755 index 0000000..68005af --- /dev/null +++ b/update/2024-11S/meanwhile/OpenScanStatistics.py @@ -0,0 +1,18 @@ +import csv + +class ScanStatistics: + def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): + self.filename = filename + self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + + def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): + data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] + + with open(self.filename, "a", newline='') as csv_file: + csv_writer = csv.writer(csv_file, delimiter=';') + + # Write header if file is empty + if csv_file.tell() == 0: + csv_writer.writerow(self.header) + + csv_writer.writerow(data) diff --git a/update/2024-11S/meanwhile/expand_root.sh b/update/2024-11S/meanwhile/expand_root.sh new file mode 100755 index 0000000..f4f7148 --- /dev/null +++ b/update/2024-11S/meanwhile/expand_root.sh @@ -0,0 +1,7 @@ +#!/bin/bash +if test -f "/boot/expand_root"; then + echo "expanding root partition" + raspi-config --expand-rootfs + rm -fr /boot/expand_root + shutdown -r now +fi diff --git a/update/2024-11S/meanwhile/flows.json b/update/2024-11S/meanwhile/flows.json index 3556333..6fa42e1 100644 --- a/update/2024-11S/meanwhile/flows.json +++ b/update/2024-11S/meanwhile/flows.json @@ -39,6 +39,14 @@ "info": "", "env": [] }, + { + "id": "87715429b0b1c9a3", + "type": "tab", + "label": "Statistics", + "disabled": false, + "info": "", + "env": [] + }, { "id": "90223f7ddc082321", "type": "ui_group", @@ -484,6 +492,25 @@ "collapse": false, "className": "" }, + { + "id": "ac59b8fb186de073", + "type": "ui_group", + "name": "[Statistics]", + "tab": "656b4eb8b15dab8f", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "656b4eb8b15dab8f", + "type": "ui_tab", + "name": "Statistics", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -636,7 +663,8 @@ "cb40b9341bd22a28", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244" + "2afb6a45c73fa244", + "9b3e6a06c82a0f52" ], "x": 645, "y": 60, @@ -1000,7 +1028,7 @@ "topic": "", "topicType": "str", "x": 120, - "y": 720, + "y": 820, "wires": [ [ "1e7457ea9c2c5e09" @@ -1017,7 +1045,7 @@ "39a502b38837273d" ], "x": 245, - "y": 720, + "y": 820, "wires": [] }, { @@ -1117,7 +1145,8 @@ "cb40b9341bd22a28", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244" + "2afb6a45c73fa244", + "9b3e6a06c82a0f52" ], "x": 1015, "y": 540, @@ -1186,6 +1215,34 @@ [] ] }, + { + "id": "e548168473aa85d6", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 5, + "width": 0, + "height": 0, + "passthru": false, + "label": "Statistics", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "5", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 100, + "y": 700, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, { "id": "6a3d9acbe097a3d2", "type": "function", @@ -2004,10 +2061,6 @@ "props": [ { "p": "payload" - }, - { - "p": "topic", - "vt": "str" } ], "repeat": "", @@ -2142,7 +2195,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\nimport os\n\ntry:\n os.remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = \"green\"\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n \ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -7032,7 +7085,7 @@ "name": "loadB", "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "noerr": 0, + "noerr": 7, "initialize": "", "finalize": "", "libs": [], @@ -7355,7 +7408,8 @@ "22ef66b0e2058be2", "9ce01c8ba97932c1", "81356177176eebcf", - "d54b85891248ba88" + "d54b85891248ba88", + "53681e53353db898" ] ] }, @@ -7707,7 +7761,7 @@ "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", "outputs": 1, "x": 270, - "y": 520, + "y": 560, "wires": [ [] ] @@ -7723,7 +7777,7 @@ "09d4a9c756161e10" ], "x": 155, - "y": 520, + "y": 560, "wires": [ [ "e2411b49791840e0" @@ -8543,6 +8597,72 @@ ] ] }, + { + "id": "e98c1b83744bb863", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Delete Aborted", + "tooltip": "Delete aborted photosets", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 420, + "y": 520, + "wires": [ + [ + "7438a5bf5fcddec4" + ] + ] + }, + { + "id": "7438a5bf5fcddec4", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "delete_aborted", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('delete_aborted'):\n save('delete_aborted', state)\n", + "outputs": 1, + "x": 600, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "53681e53353db898", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'delete_aborted'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 520, + "wires": [ + [ + "e98c1b83744bb863" + ] + ] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", @@ -9066,8 +9186,8 @@ "z": "a5557543ccff5889", "name": "", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "50eeb3e362f9027f", + "960912e90ba5b5bc" ], "x": 95, "y": 700, @@ -9160,7 +9280,7 @@ "order": 9, "width": 6, "height": 1, - "format": "\n
Download error log\n
\n", + "format": "\n
Download error log\n
\n", "storeOutMessages": false, "fwdInMessages": false, "resendOnRefresh": false, @@ -9374,5 +9494,101 @@ "wires": [ [] ] + }, + { + "id": "9b3e6a06c82a0f52", + "type": "link in", + "z": "87715429b0b1c9a3", + "name": "", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 115, + "y": 420, + "wires": [ + [ + "f128ca405d1e1e4d", + "07d7ce3dab5f1c11" + ] + ] + }, + { + "id": "cd0dc08fcb5968c8", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Successful Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 425, + "y": 407, + "wires": [] + }, + { + "id": "f128ca405d1e1e4d", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Successful Scans", + "x": 250, + "y": 420, + "wires": [ + [ + "cd0dc08fcb5968c8" + ], + [], + [] + ] + }, + { + "id": "b91b4d65f2090793", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Aborted Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 440, + "y": 340, + "wires": [] + }, + { + "id": "07d7ce3dab5f1c11", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Aborted Scans", + "x": 245, + "y": 353, + "wires": [ + [ + "b91b4d65f2090793" + ], + [], + [] + ] } ] \ No newline at end of file diff --git a/update/2024-11S/meanwhile/startup.sh b/update/2024-11S/meanwhile/startup.sh new file mode 100755 index 0000000..bdaad1d --- /dev/null +++ b/update/2024-11S/meanwhile/startup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +settings_folder="/home/pi/OpenScan/settings" + +# Generate an unique UUID that identifies that OpenScan + +if [ ! -f $settings_folder/openscan_uuid ]; then + echo $(cat /proc/sys/kernel/random/uuid) > $settings_folder/openscan_uuid +fi +echo `cat /proc/cpuinfo|grep Model|cut -d: -f2|awk '{$1=$1};1'` > $settings_folder/architecture +echo `libcamera-still --list-cameras|head -3|tail -1|cut -d: -f2|cut -d[ -f1|awk '{$1=$1};1'` > $settings_folder/camera diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index d4a0d47..1ef6eb4 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -73,12 +73,27 @@ "4": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 327504 + "filesize": 333492 }, "5": { "src": "meanwhile/settings.js", "dst": "/root/.node-red/settings.js", "filesize": 21248 + }, + "6": { + "src": "meanwhile/OpenScanStatistics.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", + "filesize": 793 + }, + "7": { + "src": "meanwhile/expand_root.sh", + "dst": "/home/pi/OpensScan/files/expand_root.sh", + "filesize": 170 + }, + "8": { + "src": "meanwhile/startup.sh", + "dst": "/home/pi/OpensScan/files/startup.sh", + "filesize": 460 } } } \ No newline at end of file From 98164e2d90ae504655a0afa61866a177826da3aa Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 28 Sep 2024 10:22:29 +0200 Subject: [PATCH 13/38] Updated beta with latest changes --- update/2024-11S/beta/OpenScan.py | 4 - update/2024-11S/beta/OpenScanStatistics.py | 18 + update/2024-11S/beta/config.txt | 57 +- update/2024-11S/beta/expand_root.sh | 7 + update/2024-11S/beta/fla.py | 504 +- update/2024-11S/beta/flows.json | 628 +- update/2024-11S/beta/flows.json.tmpl | 9852 -------------------- update/2024-11S/beta/startup.sh | 11 + update/2024-11S/update.json | 27 +- 9 files changed, 581 insertions(+), 10527 deletions(-) create mode 100755 update/2024-11S/beta/OpenScanStatistics.py create mode 100755 update/2024-11S/beta/expand_root.sh delete mode 100644 update/2024-11S/beta/flows.json.tmpl create mode 100755 update/2024-11S/beta/startup.sh diff --git a/update/2024-11S/beta/OpenScan.py b/update/2024-11S/beta/OpenScan.py index 681c78d..e634511 100644 --- a/update/2024-11S/beta/OpenScan.py +++ b/update/2024-11S/beta/OpenScan.py @@ -78,10 +78,8 @@ def add_wifi_network(ssid, password, country): with open(conf_file, "w") as f: f.write(updated_content) os.system("sudo systemctl restart wpa_supplicant@wlan0") - return True - def load_str(name): filename = basepath+'settings/'+name if not isfile(filename): @@ -201,8 +199,6 @@ def take_photo(file): model=load_str('model') - - shutter = str(load_int('cam_shutter')) saturation = load_str('cam_saturation') contrast = load_str('cam_contrast') diff --git a/update/2024-11S/beta/OpenScanStatistics.py b/update/2024-11S/beta/OpenScanStatistics.py new file mode 100755 index 0000000..68005af --- /dev/null +++ b/update/2024-11S/beta/OpenScanStatistics.py @@ -0,0 +1,18 @@ +import csv + +class ScanStatistics: + def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): + self.filename = filename + self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + + def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): + data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] + + with open(self.filename, "a", newline='') as csv_file: + csv_writer = csv.writer(csv_file, delimiter=';') + + # Write header if file is empty + if csv_file.tell() == 0: + csv_writer.writerow(self.header) + + csv_writer.writerow(data) diff --git a/update/2024-11S/beta/config.txt b/update/2024-11S/beta/config.txt index cc525ae..3bb8fef 100755 --- a/update/2024-11S/beta/config.txt +++ b/update/2024-11S/beta/config.txt @@ -2,56 +2,8 @@ # http://rpf.io/configtxt # Some settings may impact device functionality. See link above for details -# uncomment if you get no picture on HDMI for a default "safe" mode -#hdmi_safe=1 - -# uncomment the following to adjust overscan. Use positive numbers if console -# goes off screen, and negative if there is too much border -#overscan_left=16 -#overscan_right=16 -#overscan_top=16 -#overscan_bottom=16 - -# uncomment to force a console size. By default it will be display's size minus -# overscan. -#framebuffer_width=1280 -#framebuffer_height=720 - -# uncomment if hdmi display is not detected and composite is being output -#hdmi_force_hotplug=1 - -# uncomment to force a specific HDMI mode (this will force VGA) -#hdmi_group=1 -#hdmi_mode=1 - -# uncomment to force a HDMI mode rather than DVI. This can make audio work in -# DMT (computer monitor) modes -#hdmi_drive=2 - -# uncomment to increase signal to HDMI, if you have interference, blanking, or -# no display -#config_hdmi_boost=4 - -# uncomment for composite PAL -#sdtv_mode=2 - -#uncomment to overclock the arm. 700 MHz is the default. -#arm_freq=800 - -# Uncomment some or all of these to enable the optional hardware interfaces -#dtparam=i2c_arm=on -#dtparam=i2s=on -#dtparam=spi=on - -# Uncomment this to enable infrared communication. -#dtoverlay=gpio-ir,gpio_pin=17 -#dtoverlay=gpio-ir-tx,gpio_pin=18 - # Additional overlays and parameters are documented /boot/overlays/README -# Enable audio (loads snd_bcm2835) -dtparam=audio=on - # Automatically load overlays for detected cameras camera_auto_detect=1 @@ -60,7 +12,7 @@ display_auto_detect=1 # Enable DRM VC4 V3D driver dtoverlay=vc4-kms-v3d -max_framebuffers=2 +max_framebuffers=1 # Disable compensation for displays with overscan disable_overscan=1 @@ -71,15 +23,12 @@ disable_overscan=1 # (e.g. for USB device mode) or if USB support is not required. otg_mode=1 -[all] - [pi4] # Run as fast as firmware / board allows arm_boost=1 +dtoverlay=imx519,cma-512 [all] camera_auto_detect=0 gpu_mem=256 -dtoverlay=vc4-fkms-v3d -dtoverlay=imx519 -#dtoverlay=imx519,media-controller=1 +dtoverlay=imx519 \ No newline at end of file diff --git a/update/2024-11S/beta/expand_root.sh b/update/2024-11S/beta/expand_root.sh new file mode 100755 index 0000000..f4f7148 --- /dev/null +++ b/update/2024-11S/beta/expand_root.sh @@ -0,0 +1,7 @@ +#!/bin/bash +if test -f "/boot/expand_root"; then + echo "expanding root partition" + raspi-config --expand-rootfs + rm -fr /boot/expand_root + shutdown -r now +fi diff --git a/update/2024-11S/beta/fla.py b/update/2024-11S/beta/fla.py index 57f4660..026867e 100644 --- a/update/2024-11S/beta/fla.py +++ b/update/2024-11S/beta/fla.py @@ -1,14 +1,14 @@ -from flask import Flask, make_response, jsonify, request, abort, redirect +from flask import Flask, request, redirect, send_file, send_from_directory +from flask_restx import Resource, Api, Namespace from picamera2 import Picamera2 from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time -import shutil -from OpenScan import load_int, load_float, load_bool, ringlight +from OpenScan import load_int, load_float, load_bool, ringlight, motorrun import RPi.GPIO as GPIO from math import sqrt import os import math -from skimage import io, feature, color, transform +from skimage import feature, color, transform import numpy as np from scipy import ndimage import socket @@ -17,6 +17,18 @@ GPIO.setmode(GPIO.BCM) app = Flask(__name__) +api = Api(app, version='1.0', title='OpenScan API', description='API for OpenScan') + +v1 = Namespace('v1', description='API v1') +# Create a namespace for system operations +system_ns = Namespace('system', description='System operations') +camera_ns = Namespace('camera', description='Camera operations') +motor_ns = Namespace('motor', description='Motor operations') + +api.add_namespace(v1, path='/v1') +api.add_namespace(system_ns, path='/v1/system') +api.add_namespace(camera_ns, path='/v1/camera') +api.add_namespace(motor_ns, path='/v1/motor') basedir = '/home/pi/OpenScan/' timer = time() @@ -43,8 +55,8 @@ def overlay_mask(image, mask_image): return combined_rgb - def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + # Convert PIL image to grayscale image_gray = image.convert('L') @@ -69,47 +81,103 @@ def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilatio ################################################################################################################### -@app.route('/shutdown', methods=['get']) -def shutdown(): - shutdown_token = request.args.get('token') - hostname = request.host.split(":")[0] - f = open("/home/pi/OpenScan/settings/session_token", "r") - session_token = (f.readline())[:20] - if shutdown_token == session_token: - - delay = 0.1 - ringlight(2,False) - - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) - os.system('shutdown -h now') - - else: - return redirect("http://" + hostname, code=302) -################################################################################################################### -@app.route('/reboot', methods=['get']) -def reboot(): - shutdown_token = request.args.get('token') - hostname = request.host.split(":")[0] - f = open("/home/pi/OpenScan/settings/session_token", "r") - session_token = (f.readline())[:20] - if shutdown_token == session_token: - delay = 0.1 - ringlight(2,False) - - for i in range (5): - ringlight(1,True) - sleep(delay) - ringlight(1,False) - sleep(delay) - - os.system('reboot -h') - else: - return redirect("http://" + hostname, code=302) -################################################################################################################### + + +@system_ns.route('/status') +class Status(Resource): + def get(self): + ''' + Get system status + ''' + import os + import json + from time import time + + if os.path.exists('/tmp/status.json'): + try: + with open('/tmp/status.json', 'r') as status_file: + status = json.load(status_file) + + elapsed_time = time() - status['start_time'] + estimated_total_time = (elapsed_time / status['current_photo']) * status['total_photos'] + time_remaining = max(0, estimated_total_time - elapsed_time) + + status.update({ + "status": "running", + "elapsed_time": int(elapsed_time), + "estimated_total_time": int(estimated_total_time), + "time_remaining": int(time_remaining) + }) + + return status, 200 + except Exception as e: + return {"error": f"Error reading status file: {str(e)}"}, 500 + else: + return {"status": "idle"}, 200 + +@system_ns.route('/shutdown') +class Shutdown(Resource): + @system_ns.doc(params={'token': 'Shutdown token for authentication'}) + def get(self): + '''Shutdown the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('shutdown -h now') + return {'message': 'Shutting down'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/reboot') +class Reboot(Resource): + @system_ns.doc(params={'token': 'Reboot token for authentication'}) + def get(self): + '''Reboot the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('reboot -h') + return {'message': 'Rebooting'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/ringlight') +class Ringlight(Resource): + @system_ns.doc(params={'state': 'Ringlight state (0 or 1)'}) + def get(self): + '''Set ringlight state''' + state = int(request.args.get('state')) + if state == 0: + ringlight(1, False) + ringlight(2, False) + else: + ringlight(1, True) + ringlight(2, True) + return {'message': f'Ringlight set to {state}'}, 200 def plot_orb_keypoints(pil_image): downscale = 2 @@ -211,141 +279,241 @@ def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: return result -################################################################################################################### -@app.route('/picam2_init', methods=['get']) -def picam2_init(): - global picam2 - global preview_config - global capture_config - - try: - picam2.controls.AnalogueGain = 1.0 - return ({}, 200) - except: - pass - picam2 = Picamera2() +@camera_ns.route('/picam2_init') +class CameraInit(Resource): + def get(self): + '''Initialize the camera''' + global picam2 + global preview_config + global capture_config -# preview_config = picam2.create_preview_configuration(main={"size": (1280, 720)}) #--> wrong aspect ratio! -# preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}) - preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}, controls ={"FrameDurationLimits": (1, 1000000)}) + try: + picam2.controls.AnalogueGain = 1.0 + return {}, 200 + except: + pass -# preview_config = picam2.create_preview_configuration(main={"size": (2328, 1748)}) - capture_config = picam2.create_still_configuration(controls ={"FrameDurationLimits": (1, 1000000)}) - picam2.configure(preview_config) - picam2.controls.AnalogueGain = 1.0 - picam2.start() - return ({}, 200) + picam2 = Picamera2() -################################################################################################################### -@app.route('/picam2_take_photo', methods=['get']) -def picam2_take_photo(): - starttime = time() - - cropx = load_int('cam_cropx')/200 - cropy = load_int('cam_cropy')/200 - rotation = load_int('cam_rotation') - img = picam2.capture_image() - - if cam_mode !=1: - img = img.convert('RGB') - w,h = img.size - - if cropx != 0 or cropy != 0: - img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) - - if rotation == 90: - img = img.transpose(Image.ROTATE_90) - elif rotation == 180: - img= img.transpose(Image.ROTATE_180) - elif rotation == 270: - img= img.transpose(Image.ROTATE_270) - - if load_bool("cam_mask"): - if cam_mode == 1: - downscale = 0.045*1.4 - else: - downscale = 0.1*1.4 - img = create_mask(img, downscale) + preview_config = picam2.create_preview_configuration( + main={"size": (2028, 1520)}, + controls={"FrameDurationLimits": (1, 1000000)} + ) - if load_bool("cam_features") and not load_bool("cam_sharparea"): - img = plot_orb_keypoints(img) + capture_config = picam2.create_still_configuration( + controls={"FrameDurationLimits": (1, 1000000)} + ) - if load_bool("cam_sharparea") and not load_bool("cam_features"): - img2 = highlight_sharpest_areas(img) - img = overlay_mask(img, img2) - - if cam_mode != 1 and not load_bool("cam_sharparea") and not load_bool("cam_features"): - img = add_histo(img) - - img.save("/home/pi/OpenScan/tmp2/preview.jpg", quality=load_int('cam_jpeg_quality')) - print("total " + str(int(1000*(time()-starttime))) + "ms") - starttime = time() - - return ({}, 200) -################################################################################################################### -@app.route('/picam2_focus', methods=['get']) -def picam2_focus(): - focus = float(request.args.get('focus')) - picam2.set_controls({"AfMode": 0, "LensPosition": focus}) - return ({}, 200) -################################################################################################################### -@app.route('/picam2_af1', methods=['get']) -def picam2_af1(): - from libcamera import controls + picam2.configure(preview_config) + picam2.controls.AnalogueGain = 1.0 + picam2.start() + return {}, 200 + +@camera_ns.route('/picam2_take_photo') +class TakePhoto(Resource): + def get(self): + '''Take a photo and process it''' + starttime = time() + + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + img = picam2.capture_image() + + if cam_mode != 1: + img = img.convert('RGB') + w, h = img.size + + if cropx != 0 or cropy != 0: + img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) + + if rotation == 90: + img = img.transpose(Image.ROTATE_90) + elif rotation == 180: + img = img.transpose(Image.ROTATE_180) + elif rotation == 270: + img = img.transpose(Image.ROTATE_270) + + if load_bool("cam_mask"): + downscale = 0.045*1.4 if cam_mode == 1 else 0.1*1.4 + img = create_mask(img, downscale) + + if load_bool("cam_features") and not load_bool("cam_sharparea"): + img = plot_orb_keypoints(img) + + if load_bool("cam_sharparea") and not load_bool("cam_features"): + img2 = highlight_sharpest_areas(img) + img = overlay_mask(img, img2) + + if cam_mode != 1 and not load_bool("cam_sharparea") and not load_bool("cam_features"): + img = add_histo(img) + + img.save("/home/pi/OpenScan/tmp2/preview.jpg", quality=load_int('cam_jpeg_quality')) + print("total " + str(int(1000*(time()-starttime))) + "ms") + + return {'message': 'Photo taken and processed successfully'}, 200 + +@camera_ns.route('/picam2_take_photo_raw') +class TakePhotoRaw(Resource): + def get(self): + '''Take a photo and return it raw''' + starttime = time() + + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + img = picam2.capture_image() + + if cam_mode != 1: + img = img.convert('RGB') + w, h = img.size + + if cropx != 0 or cropy != 0: + img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) + + if rotation == 90: + img = img.transpose(Image.ROTATE_90) + elif rotation == 180: + img = img.transpose(Image.ROTATE_180) + elif rotation == 270: + img = img.transpose(Image.ROTATE_270) + + # Create a temporary file + + temp_filename = "/tmp/raw.jpg" + img.save(temp_filename, format='JPEG', quality=load_int('cam_jpeg_quality')) + + # Send the file and ensure it's deleted after sending + @after_request + def remove_file(response): + os.remove(temp_filename) + return response + + return send_file( + temp_filename, + mimetype='image/jpeg', + as_attachment=False + ) + +@camera_ns.route('/picam2_focus') +class picam2_focus(Resource): + def get(self): + focus = float(request.args.get('focus')) + picam2.set_controls({"AfMode": 0, "LensPosition": focus}) + return ({}, 200) - picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0, "AfRange":controls.AfRangeEnum.Macro}) - return ({}, 200) -################################################################################################################### -@app.route('/picam2_af2', methods=['get']) -def picam2_af2(): - picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0}) - return ({}, 200) +@camera_ns.route('/picam2_af1') +class AutoFocus1(Resource): + def get(self): + '''Set auto focus mode to macro''' + picam2.set_controls({"AfMode": 2, "AfTrigger": 0, "AfRange": controls.AfRangeEnum.Macro}) + return {'message': 'Auto focus set to macro mode'}, 200 + +@camera_ns.route('/picam2_af2') +class AutoFocus2(Resource): + def get(self): + '''Set auto focus mode''' + picam2.set_controls({"AfMode": 2, "AfTrigger": 0}) + return {'message': 'Auto focus mode set'}, 200 + +@camera_ns.route('/picam2_exposure') +class CameraExposure(Resource): + @camera_ns.doc(params={'exposure': 'Exposure time in microseconds'}) + def get(self): + '''Set camera exposure time''' + exposure = int(request.args.get('exposure')) + picam2.controls.AnalogueGain = 1.0 + picam2.controls.ExposureTime = exposure + return {'message': f'Exposure set to {exposure} microseconds'}, 200 + +@camera_ns.route('/picam2_contrast') +class CameraContrast(Resource): + @camera_ns.doc(params={'contrast': 'Contrast value (float)'}) + def get(self): + '''Set camera contrast''' + contrast = float(request.args.get('contrast')) + picam2.controls.Contrast = contrast + return {'message': f'Contrast set to {contrast}'}, 200 + +@camera_ns.route('/picam2_saturation') +class CameraSaturation(Resource): + @camera_ns.doc(params={'saturation': 'Saturation value (float)'}) + def get(self): + '''Set camera saturation''' + saturation = float(request.args.get('saturation')) + picam2.controls.Saturation = saturation + return {'message': f'Saturation set to {saturation}'}, 200 + +@camera_ns.route('/picam2_switch_mode') +class CameraSwitchMode(Resource): + @camera_ns.doc(params={'mode': 'Camera mode (0 or 1)'}) + def get(self): + '''Switch camera mode''' + global cam_mode + cam_mode = int(request.args.get('mode')) + if cam_mode == 1: + picam2.switch_mode(capture_config) + else: + picam2.switch_mode(preview_config) + return {'message': f'Camera mode switched to {cam_mode}'}, 200 + +@camera_ns.route('/picam2_show_mode') +class CameraShowMode(Resource): + def get(self): + '''Show current camera mode''' + global cam_mode + return {'mode': cam_mode}, 200 + +@camera_ns.route('/picam2_af') +class AutoFocus(Resource): + def get(self): + '''Trigger auto focus''' + picam2.set_controls({"AfMode": 1, "AfTrigger": 0}) # --> wait 3-5s + return {'message': 'Auto focus triggered'}, 200 + +@motor_ns.route('/motor_run') +class MotorRun(Resource): + ''' + Run a motor + ''' + @motor_ns.doc(params={ + 'motor': 'Motor name (rotor, tt, extra)', + 'angle': 'Angle to rotate (integer)', + 'ES_enable': 'Enable endstop (optional, boolean)', + 'ES_start_state': 'Endstop start state (optional, boolean)' + }) + @motor_ns.response(400, 'Bad Request') + def get(self): + '''Run a motor''' + motor = request.args.get('motor') + if not motor: + return {'error': 'Motor parameter is required'}, 400 + if motor not in ['rotor', 'tt', 'extra']: + return {'error': 'Invalid motor name'}, 400 + + try: + angle = int(request.args.get('angle')) + except (TypeError, ValueError): + return {'error': 'Angle must be an integer'}, 400 + + ES_enable = request.args.get('ES_enable', 'false').lower() == 'true' + ES_start_state = request.args.get('ES_start_state', 'true').lower() == 'true' + + try: + motorrun(motor, angle, ES_enable, ES_start_state) + except Exception as e: + return {'error': f'Error running motor: {str(e)}'}, 500 + + return {'message': f'Motor {motor} run to {angle} degrees'}, 200 -################################################################################################################### -@app.route('/picam2_exposure', methods=['get']) -def picam2_exposure(): - exposure = int(request.args.get('exposure')) - picam2.controls.AnalogueGain = 1.0 - picam2.controls.ExposureTime = exposure - return ({}, 200) -################################################################################################################### -@app.route('/picam2_contrast', methods=['get']) -def picam2_contrast(): - contrast = float(request.args.get('contrast')) - picam2.controls.Contrast = contrast - return ({}, 200) -################################################################################################################### -@app.route('/picam2_saturation', methods=['get']) -def picam2_saturation(): - saturation = float(request.args.get('saturation')) - picam2.controls.Saturation = saturation - return ({}, 200) -################################################################################################################### -@app.route('/picam2_switch_mode', methods=['get']) -def picam2_switch_mode(): - global cam_mode - cam_mode = int(request.args.get('mode')) - if cam_mode == 1: - picam2.switch_mode(capture_config) - else: - picam2.switch_mode(preview_config) - return ({}, 200) -################################################################################################################### -@app.route('/picam2_show_mode', methods=['get']) -def picam2_show_mode(): - global cam_mode - return({"mode":cam_mode},200) -################################################################################################################### -@app.route('/picam2_af', methods=['get']) -def picam2_af(): - picam2.set_controls({"AfMode": 1 ,"AfTrigger": 0}) # --> wait 3-5s - return ({}, 200) @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') + if __name__ == '__main__': # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) + diff --git a/update/2024-11S/beta/flows.json b/update/2024-11S/beta/flows.json index c003c13..6fa42e1 100644 --- a/update/2024-11S/beta/flows.json +++ b/update/2024-11S/beta/flows.json @@ -39,6 +39,14 @@ "info": "", "env": [] }, + { + "id": "87715429b0b1c9a3", + "type": "tab", + "label": "Statistics", + "disabled": false, + "info": "", + "env": [] + }, { "id": "90223f7ddc082321", "type": "ui_group", @@ -485,26 +493,23 @@ "className": "" }, { - "id": "15edc2ce885dddb3", + "id": "ac59b8fb186de073", "type": "ui_group", - "name": "Colorines", - "tab": "457102eadc9ddb6c", - "order": 8, + "name": "[Statistics]", + "tab": "656b4eb8b15dab8f", + "order": 3, "disp": true, "width": "6", "collapse": false, "className": "" }, { - "id": "33aff36289823faa", - "type": "ui_group", - "name": "Monitoring", - "tab": "457102eadc9ddb6c", - "order": 9, - "disp": true, - "width": "6", - "collapse": false, - "className": "" + "id": "656b4eb8b15dab8f", + "type": "ui_tab", + "name": "Statistics", + "icon": "dashboard", + "disabled": false, + "hidden": false }, { "id": "bc4e2c03859196c3", @@ -658,7 +663,8 @@ "cb40b9341bd22a28", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244" + "2afb6a45c73fa244", + "9b3e6a06c82a0f52" ], "x": 645, "y": 60, @@ -726,7 +732,7 @@ "type": "python3-function", "z": "e6f4d02efb300ea9", "name": "LED Status", - "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nmsg['inactivity'] = False\nreturn msg", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", "outputs": 1, "x": 270, "y": 140, @@ -792,7 +798,7 @@ "type": "python3-function", "z": "e6f4d02efb300ea9", "name": "CAM init", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", "outputs": 1, "x": 260, "y": 180, @@ -852,7 +858,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -1022,7 +1028,7 @@ "topic": "", "topicType": "str", "x": 120, - "y": 720, + "y": 820, "wires": [ [ "1e7457ea9c2c5e09" @@ -1039,7 +1045,7 @@ "39a502b38837273d" ], "x": 245, - "y": 720, + "y": 820, "wires": [] }, { @@ -1139,7 +1145,8 @@ "cb40b9341bd22a28", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244" + "2afb6a45c73fa244", + "9b3e6a06c82a0f52" ], "x": 1015, "y": 540, @@ -1208,6 +1215,34 @@ [] ] }, + { + "id": "e548168473aa85d6", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 5, + "width": 0, + "height": 0, + "passthru": false, + "label": "Statistics", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "5", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 100, + "y": 700, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, { "id": "6a3d9acbe097a3d2", "type": "function", @@ -1423,7 +1458,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "shutter", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/v1/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", "outputs": 1, "x": 510, "y": 200, @@ -1585,7 +1620,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/v1/camera/picam2_take_photo')\n\nreturn msg\n", "outputs": 1, "x": 450, "y": 800, @@ -1676,7 +1711,7 @@ "type": "function", "z": "481edaf6db5a7a54", "name": "global", - "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\n\nreturn msg", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -1695,7 +1730,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "LED", - "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1 and msg['inactivity'] is False:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", "outputs": 1, "x": 870, "y": 880, @@ -1891,8 +1926,7 @@ "y": 880, "wires": [ [ - "1fe18f3b0b52aabd", - "5a8dac2ff3dfaaa3" + "1fe18f3b0b52aabd" ] ] }, @@ -2027,10 +2061,6 @@ "props": [ { "p": "payload" - }, - { - "p": "topic", - "vt": "str" } ], "repeat": "", @@ -2080,8 +2110,8 @@ "b33d604c.5f1a6", "c8b93b42c720b9cf" ], - "x": 525, - "y": 1040, + "x": 535, + "y": 1060, "wires": [] }, { @@ -2165,7 +2195,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = \"green\"\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n \ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -2351,7 +2381,7 @@ "type": "function", "z": "481edaf6db5a7a54", "name": "enable", - "func": "var inactivity = msg['inactivity']\n\nif (inactivity) {\n global.set('light', 0);\n msg.light = 0;\n\n\n} else{\n global.set('light', 1);\n msg.light = 1;\n}\n\nmsg['inactivity'] = false\nreturn msg\n", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", @@ -2361,8 +2391,7 @@ "y": 880, "wires": [ [ - "180476141c2a44ad", - "8a4d7b329733f52b" + "180476141c2a44ad" ] ] }, @@ -2481,7 +2510,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "focus", - "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "func": "from OpenScan import camera\ncamera('/v1/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", "outputs": 1, "x": 1290, "y": 60, @@ -2684,40 +2713,6 @@ "y": 1000, "wires": [] }, - { - "id": "5a8dac2ff3dfaaa3", - "type": "debug", - "z": "481edaf6db5a7a54", - "name": "debug 6", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 880, - "y": 960, - "wires": [] - }, - { - "id": "8a4d7b329733f52b", - "type": "debug", - "z": "481edaf6db5a7a54", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 400, - "y": 920, - "wires": [] - }, { "id": "ea54fcc2.cfcc2", "type": "python3-function", @@ -4181,7 +4176,7 @@ "type": "python3-function", "z": "e43a27722b508115", "name": "check ip address", - "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\nmsg['hostname']=host\n\nreturn msg", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", "outputs": 1, "x": 250, "y": 940, @@ -7090,7 +7085,7 @@ "name": "loadB", "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "noerr": 0, + "noerr": 7, "initialize": "", "finalize": "", "libs": [], @@ -7413,7 +7408,8 @@ "22ef66b0e2058be2", "9ce01c8ba97932c1", "81356177176eebcf", - "d54b85891248ba88" + "d54b85891248ba88", + "53681e53353db898" ] ] }, @@ -7656,7 +7652,7 @@ "type": "python3-function", "z": "e43a27722b508115", "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, "x": 660, "y": 2720, @@ -7669,7 +7665,7 @@ "type": "python3-function", "z": "e43a27722b508115", "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, "x": 660, "y": 2680, @@ -7765,7 +7761,7 @@ "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", "outputs": 1, "x": 270, - "y": 520, + "y": 560, "wires": [ [] ] @@ -7781,7 +7777,7 @@ "09d4a9c756161e10" ], "x": 155, - "y": 520, + "y": 560, "wires": [ [ "e2411b49791840e0" @@ -8602,237 +8598,17 @@ ] }, { - "id": "69885a9ce218eb71", - "type": "comment", - "z": "e43a27722b508115", - "name": "Coloritos", - "info": "", - "x": 100, - "y": 3740, - "wires": [] - }, - { - "id": "dc1cde67c3022e6b", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3800, - "wires": [ - [ - "0dccca85770c7936" - ] - ] - }, - { - "id": "b63e8246ad14ad9d", - "type": "function", - "z": "e43a27722b508115", - "name": "interface-color", - "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 540, - "y": 3800, - "wires": [ - [] - ] - }, - { - "id": "b7044aa75196b521", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 3", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3800, - "wires": [ - [ - "dc1cde67c3022e6b" - ] - ] - }, - { - "id": "0dccca85770c7936", - "type": "ui_dropdown", - "z": "e43a27722b508115", - "name": "interface_color", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "15edc2ce885dddb3", - "order": 1, - "width": 0, - "height": 0, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Aburrido", - "value": "#097479", - "type": "str" - }, - { - "label": "Morado", - "value": "#790974", - "type": "str" - }, - { - "label": "Berenjena", - "value": "#79093c", - "type": "str" - }, - { - "label": "Azul", - "value": "#093c79 ", - "type": "str" - }, - { - "label": "Oliva", - "value": "#747909", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 360, - "y": 3800, - "wires": [ - [ - "b63e8246ad14ad9d" - ] - ] - }, - { - "id": "667950f6671bd1a0", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 840, - "wires": [ - [ - "b82a1cbefad51cd8" - ] - ] - }, - { - "id": "5f32d7e78e368454", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 840, - "wires": [ - [] - ] - }, - { - "id": "b82a1cbefad51cd8", - "type": "ui_text_input", + "id": "e98c1b83744bb863", + "type": "ui_switch", "z": "e43a27722b508115", - "name": "hostname", - "label": "Hostname", - "tooltip": "", - "group": "8ab79a98e536e0d6", + "name": "", + "label": "Delete Aborted", + "tooltip": "Delete aborted photosets", + "group": "d324f0b852c2df0a", "order": 1, "width": 0, "height": 0, "passthru": true, - "mode": "text", - "delay": 300, - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 360, - "y": 840, - "wires": [ - [ - "5f32d7e78e368454" - ] - ] - }, - { - "id": "5fd155711e29b1b8", - "type": "comment", - "z": "e43a27722b508115", - "name": "Monitoring", - "info": "", - "x": 100, - "y": 3860, - "wires": [] - }, - { - "id": "815702499384f118", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3920, - "wires": [ - [ - "bfdbdae28bf42ed4" - ] - ] - }, - { - "id": "464c8495f86daaa7", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "datadog_enable", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", - "outputs": 1, - "x": 520, - "y": 3920, - "wires": [ - [] - ] - }, - { - "id": "bfdbdae28bf42ed4", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "datadog_enable", - "label": "Enable Datadog", - "tooltip": "Enable Datadog monitoring", - "group": "33aff36289823faa", - "order": 1, - "width": "6", - "height": "1", - "passthru": true, "decouple": "false", "topic": "topic", "topicType": "msg", @@ -8847,132 +8623,43 @@ "offcolor": "", "animate": false, "className": "", - "x": 340, - "y": 3920, - "wires": [ - [ - "464c8495f86daaa7" - ] - ] - }, - { - "id": "f93ce2d26953341f", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "datadog_api_token", - "label": "Datadog Api Token", - "tooltip": "Datadog Api Token", - "group": "33aff36289823faa", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "password", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 350, - "y": 3960, + "x": 420, + "y": 520, "wires": [ [ - "647641e79884eb87" + "7438a5bf5fcddec4" ] ] }, { - "id": "ee668e39d213070b", - "type": "function", + "id": "7438a5bf5fcddec4", + "type": "python3-function", "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "name": "delete_aborted", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('delete_aborted'):\n save('delete_aborted', state)\n", "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3960, + "x": 600, + "y": 520, "wires": [ - [ - "f93ce2d26953341f" - ] + [] ] }, { - "id": "647641e79884eb87", + "id": "53681e53353db898", "type": "function", "z": "e43a27722b508115", - "name": "datadog_api_token", - "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "name": "loadB", + "func": "var file = 'delete_aborted'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 550, - "y": 3960, - "wires": [ - [] - ] - }, - { - "id": "ff2dea1ab9cb7776", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 4", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3960, - "wires": [ - [ - "815702499384f118", - "ee668e39d213070b" - ] - ] - }, - { - "id": "a1b81e7fe94ad4e5", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", - "outputs": 1, - "x": 530, - "y": 3740, - "wires": [ - [] - ] - }, - { - "id": "2f3a3c0e682ae862", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "restart_interface", - "group": "15edc2ce885dddb3", - "order": 1, - "width": 0, - "height": 0, - "passthru": false, - "label": "Restart Interface", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 340, - "y": 3740, + "x": 270, + "y": 520, "wires": [ [ - "a1b81e7fe94ad4e5" + "e98c1b83744bb863" ] ] }, @@ -8981,7 +8668,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", "outputs": 1, "x": 260, "y": 140, @@ -9098,7 +8785,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -9133,7 +8820,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -9417,7 +9104,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, @@ -9499,8 +9186,8 @@ "z": "a5557543ccff5889", "name": "", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "50eeb3e362f9027f", + "960912e90ba5b5bc" ], "x": 95, "y": 700, @@ -9590,10 +9277,10 @@ "z": "a5557543ccff5889", "group": "3ce32450.e0cffc", "name": "Download LOG", - "order": 10, + "order": 9, "width": 6, "height": 1, - "format": "\n
Download error log\n
\n", + "format": "\n
Download error log\n
\n", "storeOutMessages": false, "fwdInMessages": false, "resendOnRefresh": false, @@ -9809,43 +9496,98 @@ ] }, { - "id": "cde61b7de9eeaba7", - "type": "ui_button", - "z": "a5557543ccff5889", + "id": "9b3e6a06c82a0f52", + "type": "link in", + "z": "87715429b0b1c9a3", "name": "", - "group": "3ce32450.e0cffc", - "order": 9, + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 115, + "y": 420, + "wires": [ + [ + "f128ca405d1e1e4d", + "07d7ce3dab5f1c11" + ] + ] + }, + { + "id": "cd0dc08fcb5968c8", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, "width": 0, "height": 0, - "passthru": false, - "label": "Expand Root", - "tooltip": "Sets the maximum space your SD card admits", - "color": "", - "bgcolor": "", + "name": "", + "label": "Successful Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", "className": "", - "icon": "", - "payload": "expand", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 510, - "y": 1020, + "x": 425, + "y": 407, + "wires": [] + }, + { + "id": "f128ca405d1e1e4d", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Successful Scans", + "x": 250, + "y": 420, "wires": [ [ - "eab36487d201f867" - ] + "cd0dc08fcb5968c8" + ], + [], + [] ] }, { - "id": "eab36487d201f867", - "type": "python3-function", - "z": "a5557543ccff5889", + "id": "b91b4d65f2090793", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, "name": "", - "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", - "outputs": 1, - "x": 690, - "y": 1020, + "label": "Aborted Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 440, + "y": 340, + "wires": [] + }, + { + "id": "07d7ce3dab5f1c11", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Aborted Scans", + "x": 245, + "y": 353, "wires": [ + [ + "b91b4d65f2090793" + ], + [], [] ] } diff --git a/update/2024-11S/beta/flows.json.tmpl b/update/2024-11S/beta/flows.json.tmpl deleted file mode 100644 index 32e12fe..0000000 --- a/update/2024-11S/beta/flows.json.tmpl +++ /dev/null @@ -1,9852 +0,0 @@ -[ - { - "id": "e6f4d02efb300ea9", - "type": "tab", - "label": "Init", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "481edaf6db5a7a54", - "type": "tab", - "label": "Scan", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "80a3942785a26c29", - "type": "tab", - "label": "Files", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "e43a27722b508115", - "type": "tab", - "label": "Settings", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "a5557543ccff5889", - "type": "tab", - "label": "Update", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "90223f7ddc082321", - "type": "ui_group", - "name": "preview", - "tab": "e23b837a9f040895", - "order": 2, - "disp": false, - "width": "7", - "collapse": false, - "className": "" - }, - { - "id": "e23b837a9f040895", - "type": "ui_tab", - "name": "Scan", - "icon": "dashboard", - "order": 2, - "disabled": false, - "hidden": false - }, - { - "id": "5c06cb6bcc371ee6", - "type": "ui_base", - "theme": { - "name": "theme-dark", - "lightTheme": { - "default": "#0094CE", - "baseColor": "#0094CE", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "darkTheme": { - "default": "{{ darktheme-default }}", - "baseColor": "{{ darktheme-basecolor }}", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "customTheme": { - "name": "Untitled Theme 1", - "default": "#4B7930", - "baseColor": "#4B7930", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "reset": false - }, - "themeState": { - "base-color": { - "default": "{{ base-color-default }}", - "value": "{{ base-color-value }}", - "edited": false - }, - "page-titlebar-backgroundColor": { - "value": "{{ page-titlebar-bgcolor }}", - "edited": false - }, - "page-backgroundColor": { - "value": "#111111", - "edited": false - }, - "page-sidebar-backgroundColor": { - "value": "#333333", - "edited": false - }, - "group-textColor": { - "value": "#0eb8c0", - "edited": false - }, - "group-borderColor": { - "value": "#555555", - "edited": false - }, - "group-backgroundColor": { - "value": "#333333", - "edited": false - }, - "widget-textColor": { - "value": "#eeeeee", - "edited": false - }, - "widget-backgroundColor": { - "value": "{{ widget-bgcolor }}", - "edited": false - }, - "widget-borderColor": { - "value": "#333333", - "edited": false - }, - "base-font": { - "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" - } - }, - "angularTheme": { - "primary": "indigo", - "accents": "blue", - "warn": "red", - "background": "grey", - "palette": "light" - } - }, - "site": { - "name": "OpenScan", - "hideToolbar": "false", - "allowSwipe": "false", - "lockMenu": "false", - "allowTempTheme": "true", - "dateFormat": "DD/MM/YYYY", - "sizes": { - "sx": 48, - "sy": 48, - "gx": 6, - "gy": 6, - "cx": 6, - "cy": 6, - "px": 0, - "py": 0 - } - } - }, - { - "id": "34bc0fd2b0f2416c", - "type": "ui_link", - "name": "GitHub", - "link": "https://openscan-org.github.io/OpenScan-Doc/", - "icon": "fa-bookmark", - "target": "iframe", - "order": 6 - }, - { - "id": "23f75a8768250ce8", - "type": "ui_link", - "name": "Patreon", - "link": "https://www.patreon.com/OpenScan", - "icon": "fa-bookmark", - "target": "newtab", - "order": 5 - }, - { - "id": "b5fdd57b.15eda8", - "type": "ui_group", - "name": "Main", - "tab": "15a222ed.d70a7d", - "order": 1, - "disp": false, - "width": 13, - "collapse": false - }, - { - "id": "db43d646.2074c8", - "type": "ui_group", - "name": "OpenScanCloud", - "tab": "15a222ed.d70a7d", - "order": 2, - "disp": true, - "width": "6", - "collapse": false - }, - { - "id": "15a222ed.d70a7d", - "type": "ui_tab", - "name": "Files&Cloud", - "icon": "dashboard", - "order": 3, - "disabled": false, - "hidden": false - }, - { - "id": "365a30d0dfa83e95", - "type": "ui_group", - "name": "settings", - "tab": "e23b837a9f040895", - "order": 1, - "disp": false, - "width": 7, - "collapse": false, - "className": "" - }, - { - "id": "ac7409105cfecac6", - "type": "ui_group", - "name": "advanced", - "tab": "e23b837a9f040895", - "order": 3, - "disp": false, - "width": 7, - "collapse": false, - "className": "" - }, - { - "id": "729f9ea6e3513c9b", - "type": "ui_group", - "name": "Home", - "tab": "b3150b13e34b1fe8", - "order": 2, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "5b3e5aca21140e9a", - "type": "ui_group", - "name": "Update", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "b3150b13e34b1fe8", - "type": "ui_tab", - "name": "OpenScan", - "icon": "dashboard", - "order": 1, - "disabled": false, - "hidden": true - }, - { - "id": "ddbd496e.93a288", - "type": "ui_group", - "name": "Manage Updates", - "tab": "d25e08b4.5b27e8", - "order": 1, - "disp": true, - "width": "6", - "collapse": false - }, - { - "id": "3ce32450.e0cffc", - "type": "ui_group", - "name": "System & Stats", - "tab": "d25e08b4.5b27e8", - "order": 2, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "d25e08b4.5b27e8", - "type": "ui_tab", - "name": "Update & Info", - "icon": "dashboard", - "order": 4, - "disabled": false, - "hidden": false - }, - { - "id": "4390b2ebcbbe104c", - "type": "ui_group", - "name": "General", - "tab": "457102eadc9ddb6c", - "order": 1, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "8ab79a98e536e0d6", - "type": "ui_group", - "name": "Network", - "tab": "457102eadc9ddb6c", - "order": 2, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "70d0be671bf03ca7", - "type": "ui_group", - "name": "Pinout", - "tab": "457102eadc9ddb6c", - "order": 6, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "7a3279eea439bcdd", - "type": "ui_group", - "name": "Motor", - "tab": "457102eadc9ddb6c", - "order": 5, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "d324f0b852c2df0a", - "type": "ui_group", - "name": "Camera", - "tab": "457102eadc9ddb6c", - "order": 4, - "disp": true, - "width": "6", - "collapse": true, - "className": "" - }, - { - "id": "12b719cba49817c9", - "type": "ui_group", - "name": "OpenScanCloud", - "tab": "457102eadc9ddb6c", - "order": 3, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "457102eadc9ddb6c", - "type": "ui_tab", - "name": "Settings", - "icon": "dashboard", - "order": 4, - "disabled": false, - "hidden": false - }, - { - "id": "6e339d87c7d5debe", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "db43d646.2074c8", - "order": 1, - "width": 1, - "height": 1 - }, - { - "id": "33b6d7317d1524b8", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "db43d646.2074c8", - "order": 3, - "width": 1, - "height": 1 - }, - { - "id": "aaf5b874c52a58aa", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 8, - "width": 7, - "height": 1 - }, - { - "id": "2e08d4415665c939", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 9, - "width": 1, - "height": 1 - }, - { - "id": "f8d8740dcbf499fb", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 11, - "width": 1, - "height": 1 - }, - { - "id": "7ac0cb556740d159", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "365a30d0dfa83e95", - "order": 13, - "width": 1, - "height": 1 - }, - { - "id": "4de2414e29020c74", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "90223f7ddc082321", - "order": 2, - "width": 7, - "height": 1 - }, - { - "id": "ac8c60543cb04139", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "ac7409105cfecac6", - "order": 3, - "width": 7, - "height": 1 - }, - { - "id": "ce21673092264c38", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "8ab79a98e536e0d6", - "order": 3, - "width": 6, - "height": 1 - }, - { - "id": "3f7b77f8a1675d27", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "12b719cba49817c9", - "order": 7, - "width": 4, - "height": 1 - }, - { - "id": "0799b02d12fc3a14", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "7a3279eea439bcdd", - "order": 25, - "width": 6, - "height": 1 - }, - { - "id": "220493325bb79987", - "type": "ui_group", - "name": "Messaging", - "tab": "457102eadc9ddb6c", - "order": 7, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, -{ - "id": "15edc2ce885dddb3", - "type": "ui_group", - "name": "Colorines", - "tab": "457102eadc9ddb6c", - "order": 8, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, -{ - "id": "33aff36289823faa", - "type": "ui_group", - "name": "Monitoring", - "tab": "457102eadc9ddb6c", - "order": 9, - "disp": true, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "bc4e2c03859196c3", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 100, - "y": 460, - "wires": [ - [ - "949bafced17d66d6" - ] - ] - }, - { - "id": "949bafced17d66d6", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "enable", - "func": "msg.flag = global.set('flag_pw',true)\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 460, - "wires": [ - [] - ] - }, - { - "id": "a1f0ed7d5a9d670e", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "false", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "0.1", - "topic": "", - "x": 110, - "y": 60, - "wires": [ - [ - "544d20f02215011a", - "325314c1a24fe5b4", - "7a4a49f7dbe04e88", - "b1e2491c952f84c9", - "fac6626127bba4f5", - "bc2f0adaf72f97e9", - "ac242724fe7605a6" - ] - ] - }, - { - "id": "544d20f02215011a", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 330, - "y": 60, - "wires": [ - [ - "c77552216a8bb781" - ] - ] - }, - { - "id": "c77552216a8bb781", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "chk files", - "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", - "outputs": 1, - "x": 540, - "y": 60, - "wires": [ - [ - "960912e90ba5b5bc" - ] - ] - }, - { - "id": "960912e90ba5b5bc", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "started1s", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "1dffb799fdf10cbc", - "9fd259de91de1da1", - "fd0258418489839d", - "b4c843620c251c43", - "3876d5cbd248592b", - "a4c81754c148b86f", - "2e9b29c70969cf01", - "2477f81cddc8fa31", - "29036b35dfd672c6", - "592ec13d8f8923a9", - "cb40b9341bd22a28", - "d1efcd5fa9d25785", - "da61581182b7299e", - "2afb6a45c73fa244" - ], - "x": 645, - "y": 60, - "wires": [] - }, - { - "id": "325314c1a24fe5b4", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "create path", - "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", - "outputs": 1, - "x": 270, - "y": 100, - "wires": [ - [] - ] - }, - { - "id": "168d72a54504b327", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "5/0.1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "0.1", - "crontab": "", - "once": true, - "onceDelay": "5", - "topic": "", - "payload": "", - "payloadType": "str", - "x": 100, - "y": 380, - "wires": [ - [ - "6c6ef2255a7d39e5" - ] - ] - }, - { - "id": "6c6ef2255a7d39e5", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "repeat 5s/0.1s", - "mode": "link", - "links": [ - "61990987acd0f263", - "2415272f42ce468c", - "6bf8344af427a6ba" - ], - "x": 205, - "y": 380, - "wires": [] - }, - { - "id": "7a4a49f7dbe04e88", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "LED Status", - "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nmsg['inactivity'] = False\nreturn msg", - "outputs": 1, - "x": 270, - "y": 140, - "wires": [ - [ - "eb1a2387a1eeea76" - ] - ] - }, - { - "id": "b1e2491c952f84c9", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "global", - "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "fac6626127bba4f5", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "enable", - "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 280, - "wires": [ - [ - "200d4b9951b6e066" - ] - ] - }, - { - "id": "200d4b9951b6e066", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "enable", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "c8b93b42c720b9cf", - "65518f3d4e3095e5" - ], - "x": 345, - "y": 280, - "wires": [] - }, - { - "id": "bc2f0adaf72f97e9", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "CAM init", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", - "outputs": 1, - "x": 260, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "8def60b68e21e665", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "FACTORY DEFAULT", - "props": [ - { - "p": "overwrite", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": "0.1", - "topic": "", - "x": 800, - "y": 40, - "wires": [ - [ - "544d20f02215011a" - ] - ] - }, - { - "id": "eb1a2387a1eeea76", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "enable LED", - "mode": "link", - "links": [ - "592ec13d8f8923a9", - "5baf89a2682265f7" - ], - "x": 385, - "y": 140, - "wires": [] - }, - { - "id": "0d8c6bc7887fb3c2", - "type": "ui_template", - "z": "e6f4d02efb300ea9", - "group": "365a30d0dfa83e95", - "name": "shutdown+background", - "order": 14, - "width": 7, - "height": 1, - "format": "\n", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "global", - "className": "", - "x": 580, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "ac242724fe7605a6", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "rescue incomplete project", - "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", - "outputs": 1, - "x": 310, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "4468f691.103eb8", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 1, - "width": 3, - "height": 2, - "passthru": false, - "label": "SCAN", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "1", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 540, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "6560dd25.9e76c4", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 3, - "width": 3, - "height": 2, - "passthru": false, - "label": "Settings", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "3", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 100, - "y": 620, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "62cd5288.2805fc", - "type": "ui_ui_control", - "z": "e6f4d02efb300ea9", - "name": "", - "events": "all", - "x": 280, - "y": 540, - "wires": [ - [] - ] - }, - { - "id": "71e72293.91c6fc", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 2, - "width": 3, - "height": 2, - "passthru": false, - "label": "Files", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "2", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 90, - "y": 580, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "e7306ef2.3b4df", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "729f9ea6e3513c9b", - "order": 4, - "width": 3, - "height": 2, - "passthru": false, - "label": "Update&Info", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "4", - "payloadType": "num", - "topic": "", - "topicType": "str", - "x": 110, - "y": 660, - "wires": [ - [ - "62cd5288.2805fc" - ] - ] - }, - { - "id": "8955d11554f55e63", - "type": "ui_button", - "z": "e6f4d02efb300ea9", - "name": "", - "group": "5b3e5aca21140e9a", - "order": 1, - "width": 6, - "height": 3, - "passthru": false, - "label": "Install Updates", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "date", - "topic": "", - "topicType": "str", - "x": 120, - "y": 720, - "wires": [ - [ - "1e7457ea9c2c5e09" - ] - ] - }, - { - "id": "1e7457ea9c2c5e09", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "update", - "mode": "link", - "links": [ - "39a502b38837273d" - ], - "x": 245, - "y": 720, - "wires": [] - }, - { - "id": "245e4341d4fb611c", - "type": "function", - "z": "e6f4d02efb300ea9", - "name": "pinmap_v2", - "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 790, - "y": 540, - "wires": [ - [ - "627406f3611511dc" - ] - ] - }, - { - "id": "627406f3611511dc", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "write", - "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", - "outputs": 1, - "x": 930, - "y": 540, - "wires": [ - [ - "50eeb3e362f9027f" - ] - ] - }, - { - "id": "88b1bddde110298a", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "false", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": "0.1", - "topic": "", - "x": 650, - "y": 540, - "wires": [ - [ - "245e4341d4fb611c" - ] - ] - }, - { - "id": "50eeb3e362f9027f", - "type": "link out", - "z": "e6f4d02efb300ea9", - "name": "started1s", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "b4c843620c251c43", - "3876d5cbd248592b", - "a4c81754c148b86f", - "2e9b29c70969cf01", - "2477f81cddc8fa31", - "29036b35dfd672c6", - "592ec13d8f8923a9", - "cb40b9341bd22a28", - "d1efcd5fa9d25785", - "da61581182b7299e", - "2afb6a45c73fa244" - ], - "x": 1015, - "y": 540, - "wires": [] - }, - { - "id": "4f3121f158f06a61", - "type": "python3-function", - "z": "e6f4d02efb300ea9", - "name": "motor run", - "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", - "outputs": 1, - "x": 860, - "y": 580, - "wires": [ - [] - ] - }, - { - "id": "4a8a04b1e5dca8fe", - "type": "inject", - "z": "e6f4d02efb300ea9", - "name": "run rotor till endstop", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 690, - "y": 580, - "wires": [ - [ - "4f3121f158f06a61" - ] - ] - }, - { - "id": "c8167775e3401fad", - "type": "ui_template", - "z": "e6f4d02efb300ea9", - "group": "729f9ea6e3513c9b", - "name": "infotext", - "order": 4, - "width": 0, - "height": 0, - "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 580, - "y": 260, - "wires": [ - [] - ] - }, - { - "id": "6a3d9acbe097a3d2", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 120, - "wires": [ - [ - "cb6ebdabaaf7d0da" - ] - ] - }, - { - "id": "7ef6f1b5c67201fe", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 120, - "wires": [ - [] - ] - }, - { - "id": "86f7d1b2d763f6e2", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 160, - "wires": [ - [ - "c8a3fde5206ce1ae" - ] - ] - }, - { - "id": "fd799c931139764d", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 240, - "wires": [ - [ - "87be854db758a9a6" - ] - ] - }, - { - "id": "d5140d455122c49a", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 280, - "wires": [ - [ - "9daea4bd57f7a00e" - ] - ] - }, - { - "id": "194f3590dd4f6e3d", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 240, - "wires": [ - [] - ] - }, - { - "id": "2de69452e829d780", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "58e565fea35cb667", - "type": "ui_text_input", - "z": "481edaf6db5a7a54", - "name": "", - "label": "", - "tooltip": "", - "group": "365a30d0dfa83e95", - "order": 3, - "width": 4, - "height": 1, - "passthru": true, - "mode": "text", - "delay": "0", - "topic": "", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 320, - "y": 80, - "wires": [ - [ - "734ac3bff2df6837" - ] - ] - }, - { - "id": "97170908e1f4ac55", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "msg", - "func": "msg.payload=\"default\"\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 80, - "wires": [ - [ - "58e565fea35cb667" - ] - ] - }, - { - "id": "734ac3bff2df6837", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 80, - "wires": [ - [] - ] - }, - { - "id": "1dffb799fdf10cbc", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine settings", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 55, - "y": 80, - "wires": [ - [ - "97170908e1f4ac55", - "6a3d9acbe097a3d2", - "86f7d1b2d763f6e2", - "fd799c931139764d", - "d5140d455122c49a" - ] - ] - }, - { - "id": "a0156eaac7dd35e5", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "shutter", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", - "outputs": 1, - "x": 510, - "y": 200, - "wires": [ - [] - ] - }, - { - "id": "c7f5808d753480d4", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "6", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 170, - "y": 200, - "wires": [ - [ - "11f41a6030578ef4" - ] - ] - }, - { - "id": "11f41a6030578ef4", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 310, - "y": 200, - "wires": [ - [ - "a0156eaac7dd35e5" - ] - ] - }, - { - "id": "855cbcadef1163c5", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "enable", - "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 840, - "wires": [ - [ - "d1b87196ae5373ed", - "41e6a4649b6afbfb", - "2fd24f8e8e9c08b7", - "85a268108250ba88" - ] - ] - }, - { - "id": "1a443e20a973d2f1", - "type": "change", - "z": "481edaf6db5a7a54", - "name": "flag_pw true", - "rules": [ - { - "t": "set", - "p": "flag_pw", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 630, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "d1b87196ae5373ed", - "type": "change", - "z": "481edaf6db5a7a54", - "name": "flag_pw false", - "rules": [ - { - "t": "set", - "p": "flag_pw", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 430, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "03d92601c62b79d4", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "4s/0.5", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "0.1", - "crontab": "", - "once": true, - "onceDelay": "4", - "topic": "Repeat", - "payload": "0.1", - "payloadType": "str", - "x": 100, - "y": 840, - "wires": [ - [ - "855cbcadef1163c5" - ] - ] - }, - { - "id": "41e6a4649b6afbfb", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "Take Preview Shot", - "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/picam2_take_photo')\n\nreturn msg\n", - "outputs": 1, - "x": 450, - "y": 800, - "wires": [ - [ - "1a443e20a973d2f1", - "296636b7467fc745" - ] - ] - }, - { - "id": "85a268108250ba88", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "90223f7ddc082321", - "name": "preview_arducam", - "order": 1, - "width": 7, - "height": 9, - "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 450, - "y": 840, - "wires": [ - [ - "417f653ca0dfdcfc", - "180476141c2a44ad" - ] - ] - }, - { - "id": "296636b7467fc745", - "type": "link out", - "z": "481edaf6db5a7a54", - "name": "link out 1", - "mode": "link", - "links": [ - "2c58a1a66c4a8c11" - ], - "x": 575, - "y": 800, - "wires": [] - }, - { - "id": "417f653ca0dfdcfc", - "type": "delay", - "z": "481edaf6db5a7a54", - "name": "lmt 0.2/s", - "pauseType": "rate", - "timeout": "0.1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "0.2", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "allowrate": false, - "outputs": 1, - "x": 640, - "y": 840, - "wires": [ - [ - "e864254b18c23dd1" - ] - ] - }, - { - "id": "e864254b18c23dd1", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "motorrun", - "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", - "outputs": 1, - "x": 780, - "y": 840, - "wires": [ - [] - ] - }, - { - "id": "180476141c2a44ad", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "global", - "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 880, - "wires": [ - [ - "8cbdbfecbd12ef83" - ] - ] - }, - { - "id": "1fe18f3b0b52aabd", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "LED", - "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1 and msg['inactivity'] is False:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", - "outputs": 1, - "x": 870, - "y": 880, - "wires": [ - [] - ] - }, - { - "id": "2fd24f8e8e9c08b7", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "load advanced", - "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", - "outputs": 1, - "x": 440, - "y": 720, - "wires": [ - [ - "923be3b2b25224b4" - ] - ] - }, - { - "id": "923be3b2b25224b4", - "type": "ui_ui_control", - "z": "481edaf6db5a7a54", - "name": "change visibility", - "events": "all", - "x": 640, - "y": 720, - "wires": [ - [] - ] - }, - { - "id": "c8a3fde5206ce1ae", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "shutter", - "order": 4, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 310, - "y": 160, - "wires": [ - [ - "034ec9f59e50a361", - "a0156eaac7dd35e5" - ] - ] - }, - { - "id": "034ec9f59e50a361", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "rate", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 160, - "wires": [ - [] - ] - }, - { - "id": "87be854db758a9a6", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "Cropy", - "order": 7, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 310, - "y": 240, - "wires": [ - [ - "194f3590dd4f6e3d" - ] - ] - }, - { - "id": "9daea4bd57f7a00e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "Cropx", - "order": 6, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 310, - "y": 280, - "wires": [ - [ - "2de69452e829d780" - ] - ] - }, - { - "id": "cb6ebdabaaf7d0da", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "name": "Photos", - "order": 5, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 320, - "y": 120, - "wires": [ - [ - "7ef6f1b5c67201fe" - ] - ] - }, - { - "id": "82ecd3cd971cb7ea", - "type": "ui_text", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "order": 2, - "width": 3, - "height": 1, - "name": "projectname", - "label": "Projectname", - "format": "", - "layout": "row-left", - "className": "", - "x": 530, - "y": 40, - "wires": [] - }, - { - "id": "ed2974731fb8a84e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "ac7409105cfecac6", - "name": "threshold", - "order": 5, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 320, - "y": 520, - "wires": [ - [ - "06e1e19835a9816e" - ] - ] - }, - { - "id": "8cbdbfecbd12ef83", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "led", - "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", - "outputs": 1, - "x": 750, - "y": 880, - "wires": [ - [ - "1fe18f3b0b52aabd", - "5a8dac2ff3dfaaa3" - ] - ] - }, - { - "id": "06e1e19835a9816e", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 520, - "wires": [ - [] - ] - }, - { - "id": "2d5b1eb4380ae5a8", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 520, - "wires": [ - [ - "ed2974731fb8a84e" - ] - ] - }, - { - "id": "7dd287f40385922f", - "type": "ui_button", - "z": "481edaf6db5a7a54", - "name": "start ", - "group": "365a30d0dfa83e95", - "order": 10, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-play", - "payload": "", - "payloadType": "date", - "topic": "enabled", - "topicType": "str", - "x": 130, - "y": 1040, - "wires": [ - [ - "33d94a04b96a2de0", - "6d15f717d5a11002", - "9a6b30a0175a8ecd" - ] - ] - }, - { - "id": "579f2211199fd6ab", - "type": "ui_button", - "z": "481edaf6db5a7a54", - "name": "stop", - "group": "365a30d0dfa83e95", - "order": 12, - "width": 2, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "fa-stop", - "payload": "numberofphotos", - "payloadType": "global", - "topic": "", - "topicType": "str", - "x": 490, - "y": 1100, - "wires": [ - [ - "1787f08ed7070ddd", - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "1787f08ed7070ddd", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "stop", - "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", - "outputs": 1, - "x": 630, - "y": 1100, - "wires": [ - [] - ] - }, - { - "id": "e9b13dfd9f8d3711", - "type": "link out", - "z": "481edaf6db5a7a54", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "c8b93b42c720b9cf" - ], - "x": 395, - "y": 1000, - "wires": [] - }, - { - "id": "9654deebb668e012", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "1s", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": "1", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 290, - "y": 1140, - "wires": [ - [ - "c1c044f3c2139f68" - ] - ] - }, - { - "id": "8367cfa0bf5bc5df", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine", - "links": [ - "200d4b9951b6e066", - "8689e938.dd9e38", - "e9b13dfd9f8d3711", - "f20f2dbc.0f123", - "fb13752beddee9f2" - ], - "x": 45, - "y": 1040, - "wires": [ - [ - "7dd287f40385922f" - ] - ] - }, - { - "id": "fb13752beddee9f2", - "type": "link out", - "z": "481edaf6db5a7a54", - "name": "", - "mode": "link", - "links": [ - "2f4c0f98.dee2", - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "c8b93b42c720b9cf" - ], - "x": 525, - "y": 1040, - "wires": [] - }, - { - "id": "33d94a04b96a2de0", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "enable", - "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1100, - "wires": [ - [ - "579f2211199fd6ab" - ] - ] - }, - { - "id": "c1c044f3c2139f68", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "msg", - "func": "msg.enabled = false\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 490, - "y": 1140, - "wires": [ - [ - "579f2211199fd6ab" - ] - ] - }, - { - "id": "1daf9e3a5bd5ab48", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "msg", - "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 430, - "y": 1040, - "wires": [ - [ - "fb13752beddee9f2" - ] - ] - }, - { - "id": "6d15f717d5a11002", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "disable", - "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 1000, - "wires": [ - [ - "e9b13dfd9f8d3711" - ] - ] - }, - { - "id": "9a6b30a0175a8ecd", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "Routine", - "func": "from OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\n#motorrun('rotor', 140, ES_enable=True, ES_start_state=True)\n#motorrun('rotor', 10)\n\n\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", - "outputs": 1, - "x": 300, - "y": 1040, - "wires": [ - [ - "1daf9e3a5bd5ab48", - "795c85ad4f109567" - ] - ] - }, - { - "id": "afe47a9eaae6f67f", - "type": "ui_text", - "z": "481edaf6db5a7a54", - "group": "365a30d0dfa83e95", - "order": 1, - "width": 7, - "height": 1, - "name": "", - "label": "Current Status:", - "format": " {{msg.payload}} ", - "layout": "row-spread", - "className": "", - "x": 340, - "y": 40, - "wires": [] - }, - { - "id": "8608517d0567d63f", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadS", - "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 40, - "wires": [ - [ - "afe47a9eaae6f67f" - ] - ] - }, - { - "id": "6bf8344af427a6ba", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start status", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 55, - "y": 40, - "wires": [ - [ - "8608517d0567d63f" - ] - ] - }, - { - "id": "78cfe60013a1bea4", - "type": "ui_switch", - "z": "481edaf6db5a7a54", - "name": "", - "label": "Show Sharpness", - "tooltip": "", - "group": "ac7409105cfecac6", - "order": 2, - "width": 7, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 350, - "y": 380, - "wires": [ - [ - "9774e7ad3b506354" - ] - ] - }, - { - "id": "9774e7ad3b506354", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "focus", - "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", - "outputs": 1, - "x": 510, - "y": 380, - "wires": [ - [ - "f0af909f3e739b22" - ] - ] - }, - { - "id": "39c744466a21735e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "90223f7ddc082321", - "name": "focus_min", - "order": 3, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 990, - "y": 40, - "wires": [ - [ - "fa181d22775c2ce6" - ] - ] - }, - { - "id": "61aab497fa50898e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "90223f7ddc082321", - "name": "focus_max", - "order": 4, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 990, - "y": 80, - "wires": [ - [ - "c615034ea6b26174" - ] - ] - }, - { - "id": "5e83b653850fa16e", - "type": "ui_template", - "z": "481edaf6db5a7a54", - "group": "ac7409105cfecac6", - "name": "stacksize", - "order": 4, - "width": 7, - "height": 1, - "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", - "storeOutMessages": true, - "fwdInMessages": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 320, - "y": 480, - "wires": [ - [ - "237c2135cdad86ea" - ] - ] - }, - { - "id": "dd7fb8791d34c751", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "enable", - "func": "var inactivity = msg['inactivity']\n\nif (inactivity) {\n global.set('light', 0);\n msg.light = 0;\n\n\n} else{\n global.set('light', 1);\n msg.light = 1;\n}\n\nmsg['inactivity'] = false\nreturn msg\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 250, - "y": 880, - "wires": [ - [ - "180476141c2a44ad", - "8a4d7b329733f52b" - ] - ] - }, - { - "id": "5baf89a2682265f7", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "enable led", - "links": [ - "eb1a2387a1eeea76" - ], - "x": 145, - "y": 880, - "wires": [ - [ - "dd7fb8791d34c751" - ] - ] - }, - { - "id": "6a26e8a7253d708c", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 830, - "y": 40, - "wires": [ - [ - "39c744466a21735e" - ] - ] - }, - { - "id": "35ad7e55833836c1", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadF", - "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 830, - "y": 80, - "wires": [ - [ - "61aab497fa50898e" - ] - ] - }, - { - "id": "9fd259de91de1da1", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine settings", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 735, - "y": 40, - "wires": [ - [ - "6a26e8a7253d708c", - "35ad7e55833836c1" - ] - ] - }, - { - "id": "fa181d22775c2ce6", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "rate", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1150, - "y": 40, - "wires": [ - [ - "ae5ee8787145906d" - ] - ] - }, - { - "id": "c615034ea6b26174", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "rate", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1150, - "y": 80, - "wires": [ - [ - "ae5ee8787145906d" - ] - ] - }, - { - "id": "ae5ee8787145906d", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "focus", - "func": "from OpenScan import camera\ncamera('/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", - "outputs": 1, - "x": 1290, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "f0af909f3e739b22", - "type": "ui_switch", - "z": "481edaf6db5a7a54", - "name": "", - "label": "Show Features", - "tooltip": "", - "group": "ac7409105cfecac6", - "order": 1, - "width": 7, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 340, - "y": 420, - "wires": [ - [ - "710fc2dbb5ef0167" - ] - ] - }, - { - "id": "710fc2dbb5ef0167", - "type": "python3-function", - "z": "481edaf6db5a7a54", - "name": "focus", - "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", - "outputs": 1, - "x": 510, - "y": 420, - "wires": [ - [ - "78cfe60013a1bea4" - ] - ] - }, - { - "id": "d93c2b67bcf23b9a", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "loadI", - "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 480, - "wires": [ - [ - "5e83b653850fa16e" - ] - ] - }, - { - "id": "237c2135cdad86ea", - "type": "function", - "z": "481edaf6db5a7a54", - "name": "write", - "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 480, - "wires": [ - [] - ] - }, - { - "id": "fd0258418489839d", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "start routine settings", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 95, - "y": 480, - "wires": [ - [ - "2d5b1eb4380ae5a8", - "d93c2b67bcf23b9a" - ] - ] - }, - { - "id": "c6f281351e11b58a", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "", - "props": [ - { - "p": "enabled", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 190, - "y": 600, - "wires": [ - [ - "ed2974731fb8a84e" - ] - ] - }, - { - "id": "ca4ca7fae36d312d", - "type": "inject", - "z": "481edaf6db5a7a54", - "name": "", - "props": [ - { - "p": "enabled", - "v": "false", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 190, - "y": 640, - "wires": [ - [ - "ed2974731fb8a84e" - ] - ] - }, - { - "id": "c8b93b42c720b9cf", - "type": "link in", - "z": "481edaf6db5a7a54", - "name": "sharpness/features", - "links": [ - "200d4b9951b6e066", - "e9b13dfd9f8d3711", - "fb13752beddee9f2" - ], - "x": 85, - "y": 380, - "wires": [ - [ - "78cfe60013a1bea4" - ] - ] - }, - { - "id": "795c85ad4f109567", - "type": "debug", - "z": "481edaf6db5a7a54", - "name": "debug 5", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 620, - "y": 1000, - "wires": [] - }, -{ - "id": "5a8dac2ff3dfaaa3", - "type": "debug", - "z": "481edaf6db5a7a54", - "name": "debug 6", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 880, - "y": 960, - "wires": [] - }, - { - "id": "8a4d7b329733f52b", - "type": "debug", - "z": "481edaf6db5a7a54", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 400, - "y": 920, - "wires": [] - }, - { - "id": "ea54fcc2.cfcc2", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "get dirs", - "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", - "outputs": 1, - "x": 480, - "y": 180, - "wires": [ - [ - "f3662f8c7d3d7a2d", - "01e4783e148c6698" - ] - ] - }, - { - "id": "2f4c0f98.dee2", - "type": "link in", - "z": "80a3942785a26c29", - "name": "filelist", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc", - "a4f09e25.02569", - "ed35109311335099", - "fb13752beddee9f2" - ], - "x": 355, - "y": 220, - "wires": [ - [ - "ea54fcc2.cfcc2" - ] - ] - }, - { - "id": "952ce286.4ffd4", - "type": "ui_text", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "order": 4, - "width": 6, - "height": 1, - "name": "Status", - "label": "Status", - "format": "{{msg.status}}", - "layout": "row-spread", - "className": "", - "x": 250, - "y": 60, - "wires": [] - }, - { - "id": "d4383424.7807c8", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "upload", - "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", - "outputs": 2, - "x": 530, - "y": 460, - "wires": [ - [ - "9a132ab1.b21658" - ], - [ - "3d16b3789632784d", - "9a132ab1.b21658" - ] - ] - }, - { - "id": "50710948.71c308", - "type": "change", - "z": "80a3942785a26c29", - "name": "set", - "rules": [ - { - "t": "set", - "p": "set", - "pt": "global", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 750, - "y": 180, - "wires": [ - [ - "ada1b6f7cccc9344", - "85839a17fb7b58b9" - ] - ] - }, - { - "id": "834046a4.647938", - "type": "ui_text", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "order": 5, - "width": 6, - "height": 1, - "name": "Set", - "label": "Set:", - "format": "{{msg.payload.Name}}", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 220, - "wires": [] - }, - { - "id": "9a132ab1.b21658", - "type": "change", - "z": "80a3942785a26c29", - "name": "flag.true", - "rules": [ - { - "t": "set", - "p": "flag", - "pt": "global", - "to": "true", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 780, - "y": 460, - "wires": [ - [ - "8689e938.dd9e38" - ] - ] - }, - { - "id": "3c67e97b.9d19a6", - "type": "function", - "z": "80a3942785a26c29", - "name": "enable", - "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 130, - "y": 340, - "wires": [ - [ - "7a93d1e18254685c", - "e434ef42bd6b92e8", - "d5d840183025d91b", - "ab9e90ab5a53a0dd", - "478994f671a3907d" - ] - ] - }, - { - "id": "bfc01f26.c32cf", - "type": "change", - "z": "80a3942785a26c29", - "name": "flag.false", - "rules": [ - { - "t": "set", - "p": "flag", - "pt": "global", - "to": "false", - "tot": "bool" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 420, - "y": 500, - "wires": [ - [ - "f20f2dbc.0f123" - ] - ] - }, - { - "id": "b33d604c.5f1a6", - "type": "link in", - "z": "80a3942785a26c29", - "name": "enable cloud", - "links": [ - "4082b136.dae18", - "8689e938.dd9e38", - "bd75f33b8a57c522", - "e9b13dfd9f8d3711", - "f20f2dbc.0f123", - "fb13752beddee9f2" - ], - "x": 35, - "y": 340, - "wires": [ - [ - "3c67e97b.9d19a6" - ] - ] - }, - { - "id": "f6bd1a04.470838", - "type": "change", - "z": "80a3942785a26c29", - "name": "set", - "rules": [ - { - "t": "set", - "p": "payload", - "pt": "msg", - "to": "set", - "tot": "global" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 410, - "y": 460, - "wires": [ - [ - "d4383424.7807c8" - ] - ] - }, - { - "id": "4082b136.dae18", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "b33d604c.5f1a6", - "87574a42938afec4" - ], - "x": 715, - "y": 140, - "wires": [] - }, - { - "id": "f20f2dbc.0f123", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "149e2e46b9623a2d" - ], - "x": 515, - "y": 500, - "wires": [] - }, - { - "id": "8689e938.dd9e38", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "8367cfa0bf5bc5df", - "b33d604c.5f1a6", - "149e2e46b9623a2d" - ], - "x": 875, - "y": 460, - "wires": [] - }, - { - "id": "15de0ebb.616d61", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 380, - "wires": [ - [ - "a7d89487.ee8858" - ] - ] - }, - { - "id": "a7d89487.ee8858", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", - "outputs": 1, - "x": 690, - "y": 380, - "wires": [ - [ - "a4f09e25.02569" - ] - ] - }, - { - "id": "a4f09e25.02569", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "2f4c0f98.dee2" - ], - "x": 775, - "y": 360, - "wires": [] - }, - { - "id": "7a93d1e18254685c", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "809c9427e14e2448", - "92c98e6ce7cd25f9" - ], - "x": 235, - "y": 500, - "wires": [] - }, - { - "id": "4d99c601c9881680", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "refresh", - "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", - "outputs": 2, - "x": 320, - "y": 180, - "wires": [ - [ - "ea54fcc2.cfcc2", - "b42e061fb1f1f3d7" - ], - [ - "6434e713f088012b" - ] - ] - }, - { - "id": "372e95797a3f2f3b", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "limit :)", - "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", - "outputs": 1, - "x": 90, - "y": 220, - "wires": [ - [ - "573edbfdb7500ddc" - ] - ] - }, - { - "id": "573edbfdb7500ddc", - "type": "delay", - "z": "80a3942785a26c29", - "name": "", - "pauseType": "rate", - "timeout": "5", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 230, - "y": 220, - "wires": [ - [ - "c46e10b9c201913e" - ] - ] - }, - { - "id": "dacb1f078b624e10", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 550, - "y": 340, - "wires": [ - [ - "c8d65cc7c2ff7c36" - ] - ] - }, - { - "id": "92c98e6ce7cd25f9", - "type": "link in", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "7a93d1e18254685c", - "bd75f33b8a57c522" - ], - "x": 35, - "y": 180, - "wires": [ - [ - "c46e10b9c201913e" - ] - ] - }, - { - "id": "3d16b3789632784d", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "Terms", - "x": 770, - "y": 500, - "wires": [ - [] - ] - }, - { - "id": "6434e713f088012b", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "Terms", - "x": 470, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "c8d65cc7c2ff7c36", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "del", - "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", - "outputs": 1, - "x": 690, - "y": 340, - "wires": [ - [ - "a4f09e25.02569" - ] - ] - }, - { - "id": "f4e9a4bd79b4221f", - "type": "function", - "z": "80a3942785a26c29", - "name": "msg", - "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 340, - "wires": [ - [ - "dacb1f078b624e10" - ] - ] - }, - { - "id": "2806bf08ea21216d", - "type": "function", - "z": "80a3942785a26c29", - "name": "msg", - "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 380, - "wires": [ - [ - "15de0ebb.616d61" - ] - ] - }, - { - "id": "61990987acd0f263", - "type": "link in", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "6c6ef2255a7d39e5" - ], - "x": 45, - "y": 60, - "wires": [ - [ - "51579603bce21e98" - ] - ] - }, - { - "id": "e8e488a6dd5d0b33", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "Download", - "order": 8, - "width": 3, - "height": 1, - "format": "\n
Download\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 880, - "y": 260, - "wires": [ - [] - ] - }, - { - "id": "0c387c0291d6c131", - "type": "function", - "z": "80a3942785a26c29", - "name": "msg", - "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 750, - "y": 260, - "wires": [ - [ - "e8e488a6dd5d0b33" - ] - ] - }, - { - "id": "e5f38b4a07a5e278", - "type": "link in", - "z": "80a3942785a26c29", - "name": "", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 655, - "y": 220, - "wires": [ - [ - "834046a4.647938" - ] - ] - }, - { - "id": "e434ef42bd6b92e8", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "upload2", - "order": 7, - "width": 3, - "height": 1, - "format": "upload", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 280, - "y": 460, - "wires": [ - [ - "f6bd1a04.470838" - ] - ] - }, - { - "id": "c46e10b9c201913e", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "refresh", - "order": 2, - "width": 4, - "height": 1, - "format": "refresh{{msg.text}}", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 160, - "y": 180, - "wires": [ - [ - "372e95797a3f2f3b", - "4d99c601c9881680" - ] - ] - }, - { - "id": "d5d840183025d91b", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "del set", - "order": 11, - "width": 2, - "height": 1, - "format": "delete set", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 270, - "y": 380, - "wires": [ - [ - "2806bf08ea21216d" - ] - ] - }, - { - "id": "ab9e90ab5a53a0dd", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "del ", - "order": 10, - "width": 2, - "height": 1, - "format": "delete all", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 270, - "y": 340, - "wires": [ - [ - "f4e9a4bd79b4221f" - ] - ] - }, - { - "id": "478994f671a3907d", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "combine", - "order": 9, - "width": 2, - "height": 1, - "format": "combine", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 280, - "y": 540, - "wires": [ - [ - "51bfd0fb7b1d292e" - ] - ] - }, - { - "id": "189c1eed09624a7b", - "type": "function", - "z": "80a3942785a26c29", - "name": "combine", - "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 580, - "wires": [ - [ - "1493398979a63775" - ] - ] - }, - { - "id": "51bfd0fb7b1d292e", - "type": "function", - "z": "80a3942785a26c29", - "name": "combine", - "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 420, - "y": 540, - "wires": [ - [] - ] - }, - { - "id": "da325be8e74179be", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "combine", - "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", - "outputs": 1, - "x": 560, - "y": 580, - "wires": [ - [ - "ed35109311335099" - ] - ] - }, - { - "id": "ed35109311335099", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "809c9427e14e2448", - "2f4c0f98.dee2" - ], - "x": 655, - "y": 580, - "wires": [] - }, - { - "id": "1493398979a63775", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "Combine", - "x": 420, - "y": 580, - "wires": [ - [ - "da325be8e74179be" - ] - ] - }, - { - "id": "ada1b6f7cccc9344", - "type": "link out", - "z": "80a3942785a26c29", - "name": "combine", - "mode": "link", - "links": [ - "6dd356510c446cf4" - ], - "x": 835, - "y": 180, - "wires": [] - }, - { - "id": "6dd356510c446cf4", - "type": "link in", - "z": "80a3942785a26c29", - "name": "combine", - "links": [ - "ada1b6f7cccc9344" - ], - "x": 175, - "y": 580, - "wires": [ - [ - "189c1eed09624a7b" - ] - ] - }, - { - "id": "b42e061fb1f1f3d7", - "type": "link out", - "z": "80a3942785a26c29", - "name": "", - "mode": "link", - "links": [ - "397ab7f44b893c89", - "3876d5cbd248592b" - ], - "x": 435, - "y": 140, - "wires": [] - }, - { - "id": "b99505440832439f", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "diskspace", - "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", - "outputs": 1, - "x": 800, - "y": 100, - "wires": [ - [ - "92047434f8e9f927" - ] - ] - }, - { - "id": "92047434f8e9f927", - "type": "ui_toast", - "z": "80a3942785a26c29", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 950, - "y": 100, - "wires": [ - [] - ] - }, - { - "id": "f3662f8c7d3d7a2d", - "type": "delay", - "z": "80a3942785a26c29", - "name": "", - "pauseType": "rate", - "timeout": "5", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "minute", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": true, - "allowrate": false, - "outputs": 1, - "x": 650, - "y": 100, - "wires": [ - [ - "b99505440832439f" - ] - ] - }, - { - "id": "51579603bce21e98", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "read", - "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", - "outputs": 1, - "x": 130, - "y": 60, - "wires": [ - [ - "952ce286.4ffd4" - ] - ] - }, - { - "id": "9a5baae623355f9d", - "type": "ui_template", - "z": "80a3942785a26c29", - "group": "db43d646.2074c8", - "name": "preview", - "order": 6, - "width": 6, - "height": 6, - "format": "
\n\n\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 1020, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "85839a17fb7b58b9", - "type": "python3-function", - "z": "80a3942785a26c29", - "name": "preview", - "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", - "outputs": 1, - "x": 880, - "y": 220, - "wires": [ - [ - "9a5baae623355f9d" - ] - ] - }, - { - "id": "01e4783e148c6698", - "type": "ui_table", - "z": "80a3942785a26c29", - "group": "b5fdd57b.15eda8", - "name": "", - "order": 1, - "width": 13, - "height": 7, - "columns": [ - { - "field": "Date", - "title": "Date", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Name", - "title": "Name", - "width": "150", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Photos", - "title": "Photos", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Duration", - "title": "ΔT", - "width": "60", - "align": "left", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Size", - "title": "Size", - "width": "80", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - }, - { - "field": "Status", - "title": "Status", - "width": "140", - "align": "center", - "formatter": "plaintext", - "formatterParams": { - "target": "_blank" - } - } - ], - "outputs": 1, - "cts": true, - "x": 610, - "y": 180, - "wires": [ - [ - "4082b136.dae18", - "50710948.71c308", - "834046a4.647938", - "0c387c0291d6c131" - ] - ] - }, - { - "id": "cb3437ec113e1b6f", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "", - "label": "SSH", - "tooltip": "", - "group": "4390b2ebcbbe104c", - "order": 3, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 390, - "y": 360, - "wires": [ - [ - "c24f61b87e3226dd" - ] - ] - }, - { - "id": "60fd0adce1cfeb82", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "", - "label": "Samba", - "tooltip": "", - "group": "4390b2ebcbbe104c", - "order": 4, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "test2", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 400, - "y": 400, - "wires": [ - [ - "441d3ef525e901da" - ] - ] - }, - { - "id": "c24f61b87e3226dd", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "ssh", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", - "outputs": 1, - "x": 530, - "y": 360, - "wires": [ - [] - ] - }, - { - "id": "c013e836e759a085", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "", - "group": "4390b2ebcbbe104c", - "order": 2, - "width": 6, - "height": 1, - "passthru": false, - "label": "Terms Of Use", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 120, - "y": 320, - "wires": [ - [ - "b78346ca3ce70c68" - ] - ] - }, - { - "id": "f0d8dbcca76a1926", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "Agree", - "cancel": "Disagree", - "raw": true, - "className": "", - "topic": "", - "name": "", - "x": 410, - "y": 320, - "wires": [ - [ - "e95b86cbac1b03b9" - ] - ] - }, - { - "id": "34374044c0030625", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "General", - "group": "4390b2ebcbbe104c", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 740, - "y": 220, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "b2b6bf23c9989133", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Pinout", - "group": "70d0be671bf03ca7", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 430, - "y": 220, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "441d3ef525e901da", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "smb", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", - "outputs": 1, - "x": 530, - "y": 400, - "wires": [ - [] - ] - }, - { - "id": "3256bab150113a48", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Motor", - "group": "7a3279eea439bcdd", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 430, - "y": 140, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "7a186669a17daa71", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "camera", - "group": "d324f0b852c2df0a", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 420, - "y": 180, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "edac7dd292e7e486", - "type": "comment", - "z": "e43a27722b508115", - "name": "General Settings", - "info": "", - "x": 120, - "y": 280, - "wires": [] - }, - { - "id": "161b52034e578ee2", - "type": "comment", - "z": "e43a27722b508115", - "name": "Network", - "info": "", - "x": 100, - "y": 720, - "wires": [] - }, - { - "id": "f6d6cc35679ede63", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "more sets", - "label": "Advanced Settings", - "tooltip": "", - "group": "4390b2ebcbbe104c", - "order": 5, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 400, - "y": 480, - "wires": [ - [ - "f06a7bcad524e9f9" - ] - ] - }, - { - "id": "29745a36fc157f3f", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "more sets", - "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", - "outputs": 2, - "x": 820, - "y": 480, - "wires": [ - [ - "8750ad979e9ea246" - ], - [ - "f6d6cc35679ede63" - ] - ] - }, - { - "id": "bf23328f9fb11b22", - "type": "ui_ui_control", - "z": "e43a27722b508115", - "name": "change visibility", - "events": "all", - "x": 600, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "b37be1d222bc70c9", - "type": "inject", - "z": "e43a27722b508115", - "name": "1s_repeater", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "1", - "crontab": "", - "once": true, - "onceDelay": "2", - "topic": "", - "payload": "", - "payloadType": "date", - "x": 150, - "y": 60, - "wires": [ - [ - "89eedf29b404f750" - ] - ] - }, - { - "id": "89eedf29b404f750", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "load advanced", - "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", - "outputs": 2, - "x": 360, - "y": 60, - "wires": [ - [ - "bf23328f9fb11b22" - ], - [ - "bf23328f9fb11b22" - ] - ] - }, - { - "id": "2050de5d9e02f69f", - "type": "comment", - "z": "e43a27722b508115", - "name": "Info Texts", - "info": "", - "x": 100, - "y": 140, - "wires": [] - }, - { - "id": "ded3086945a6d4b5", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "check ip address", - "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\nmsg['hostname']=host\n\nreturn msg", - "outputs": 1, - "x": 250, - "y": 940, - "wires": [ - [ - "3cfe464506f46ecd" - ] - ] - }, - { - "id": "3cfe464506f46ecd", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "8ab79a98e536e0d6", - "order": 1, - "width": 0, - "height": 0, - "name": "", - "label": "Your local IP:", - "format": "{{msg.ip}}", - "layout": "row-spread", - "className": "", - "x": 430, - "y": 940, - "wires": [] - }, - { - "id": "bd206ad109831e6a", - "type": "comment", - "z": "e43a27722b508115", - "name": "OpenScanCloud", - "info": "", - "x": 120, - "y": 1260, - "wires": [] - }, - { - "id": "b70a9a665c1e4d36", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Cloud-settings", - "group": "12b719cba49817c9", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 740, - "y": 260, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "c9f0566601a3e130", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "12b719cba49817c9", - "order": 4, - "width": 0, - "height": 0, - "name": "", - "label": "Max. Number of Photos:", - "format": "{{msg.limit_photos}}", - "layout": "row-spread", - "className": "", - "x": 410, - "y": 1400, - "wires": [] - }, - { - "id": "9bd86d27ea499a2a", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "12b719cba49817c9", - "order": 5, - "width": 0, - "height": 0, - "name": "", - "label": "Max. Filesize (GB):", - "format": "{{msg.limit_filesize}}", - "layout": "row-spread", - "className": "", - "x": 390, - "y": 1440, - "wires": [] - }, - { - "id": "2c37f7030810d234", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "12b719cba49817c9", - "order": 3, - "width": 0, - "height": 0, - "name": "", - "label": "Credit (GB):", - "format": "{{msg.credit}}", - "layout": "row-spread", - "className": "", - "x": 370, - "y": 1480, - "wires": [] - }, - { - "id": "f40286c18afd4501", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "save", - "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", - "outputs": 2, - "x": 750, - "y": 1340, - "wires": [ - [ - "455a5266017ea121", - "50f73cee213ec05c" - ], - [ - "264eece408043021" - ] - ] - }, - { - "id": "455a5266017ea121", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "topic": "", - "name": "", - "x": 890, - "y": 1300, - "wires": [ - [] - ] - }, - { - "id": "c368df68593bc2bf", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "Token", - "tooltip": "", - "group": "12b719cba49817c9", - "order": 2, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 350, - "y": 1360, - "wires": [ - [ - "18fd1afa768187b3" - ] - ] - }, - { - "id": "18fd1afa768187b3", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "Save?", - "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", - "outputs": 2, - "x": 470, - "y": 1360, - "wires": [ - [ - "418aea2ec65573a0" - ], - [ - "9792c89c5f4429f9" - ] - ] - }, - { - "id": "f90a98899b7a71d0", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "text", - "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", - "outputs": 1, - "x": 230, - "y": 1360, - "wires": [ - [ - "c368df68593bc2bf" - ] - ] - }, - { - "id": "b4c843620c251c43", - "type": "link in", - "z": "e43a27722b508115", - "name": "token", - "links": [ - "960912e90ba5b5bc", - "50f73cee213ec05c", - "9792c89c5f4429f9", - "50eeb3e362f9027f" - ], - "x": 75, - "y": 1360, - "wires": [ - [ - "f90a98899b7a71d0" - ] - ] - }, - { - "id": "418aea2ec65573a0", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 610, - "y": 1340, - "wires": [ - [ - "f40286c18afd4501" - ] - ] - }, - { - "id": "9792c89c5f4429f9", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "mode": "link", - "links": [ - "b4c843620c251c43" - ], - "x": 555, - "y": 1380, - "wires": [] - }, - { - "id": "264eece408043021", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "links": [ - "5d267acc10020091", - "3876d5cbd248592b" - ], - "x": 835, - "y": 1380, - "wires": [] - }, - { - "id": "3876d5cbd248592b", - "type": "link in", - "z": "e43a27722b508115", - "name": "OSCparameters", - "links": [ - "960912e90ba5b5bc", - "264eece408043021", - "b42e061fb1f1f3d7", - "50eeb3e362f9027f" - ], - "x": 75, - "y": 1400, - "wires": [ - [ - "5daca3ec47f8e7fc" - ] - ] - }, - { - "id": "50f73cee213ec05c", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "links": [ - "b4c843620c251c43", - "5d267acc10020091" - ], - "x": 835, - "y": 1340, - "wires": [] - }, - { - "id": "95578e54a9b61cba", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 250, - "y": 1540, - "wires": [ - [ - "d7a5693da7855da8" - ] - ] - }, - { - "id": "d7a5693da7855da8", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", - "outputs": 2, - "x": 390, - "y": 1540, - "wires": [ - [ - "2b02b97dd1614e52" - ], - [ - "183a629accb417b1" - ] - ] - }, - { - "id": "183a629accb417b1", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 530, - "y": 1580, - "wires": [ - [] - ] - }, - { - "id": "2b02b97dd1614e52", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 530, - "y": 1540, - "wires": [ - [ - "3e4c15d7b538f816" - ] - ] - }, - { - "id": "3bf622f344172721", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "prompt", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "SUBMIT", - "cancel": "Cancel", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 810, - "y": 1540, - "wires": [ - [ - "e431cb2b8d217cee" - ] - ] - }, - { - "id": "e431cb2b8d217cee", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", - "outputs": 1, - "x": 950, - "y": 1540, - "wires": [ - [ - "106874534890f229" - ] - ] - }, - { - "id": "a38d7fde5c73210f", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Register", - "group": "12b719cba49817c9", - "order": 6, - "width": 2, - "height": 1, - "passthru": false, - "label": "Register", - "tooltip": "testtesttest", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "Please enter your email address:", - "payloadType": "str", - "topic": "Requesting an OpenScanCloud Token", - "topicType": "str", - "x": 100, - "y": 1540, - "wires": [ - [ - "95578e54a9b61cba" - ] - ] - }, - { - "id": "106874534890f229", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 1090, - "y": 1540, - "wires": [ - [] - ] - }, - { - "id": "5daca3ec47f8e7fc", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", - "outputs": 1, - "x": 230, - "y": 1400, - "wires": [ - [ - "c9f0566601a3e130", - "9bd86d27ea499a2a", - "2c37f7030810d234" - ] - ] - }, - { - "id": "f34de19d4cf810a9", - "type": "comment", - "z": "e43a27722b508115", - "name": "Motor", - "info": "", - "x": 90, - "y": 1740, - "wires": [] - }, - { - "id": "26c2b58e21f97475", - "type": "comment", - "z": "e43a27722b508115", - "name": "Camera", - "info": "", - "x": 90, - "y": 2500, - "wires": [] - }, - { - "id": "a8ec972bad47a9a8", - "type": "comment", - "z": "e43a27722b508115", - "name": "Pinout", - "info": "", - "x": 90, - "y": 2960, - "wires": [] - }, - { - "id": "b03e8b51187e88eb", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "Rotor_delay (ms)", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 16, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.01", - "max": "0.2", - "step": "0.005", - "className": "", - "x": 450, - "y": 2100, - "wires": [ - [ - "11fd3363416433f9" - ] - ] - }, - { - "id": "6aae9d4fddf08cc0", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt delay", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 30, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.01", - "max": "0.2", - "step": "0.005", - "className": "", - "x": 420, - "y": 2340, - "wires": [ - [ - "e50492d1e18f43c6" - ] - ] - }, - { - "id": "543e1690693acbeb", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_acc", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 18, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.1", - "max": "2", - "step": "0.1", - "className": "", - "x": 420, - "y": 2140, - "wires": [ - [ - "e8b24efb0f30288e" - ] - ] - }, - { - "id": "9a56c087d941f1da", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_accramp", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 20, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "100", - "max": "5000", - "step": "100", - "className": "", - "x": 440, - "y": 2180, - "wires": [ - [ - "29f576be9e292232" - ] - ] - }, - { - "id": "dfdebe10dbf0e198", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotor_stepsperrotation", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 14, - "width": 3, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 460, - "y": 2060, - "wires": [ - [ - "78e256083f59f66f" - ] - ] - }, - { - "id": "af8dfe78cbd0c301", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 19, - "width": 3, - "height": 1, - "name": "rotor Accramp", - "label": "Acceleration ramp", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 2140, - "wires": [] - }, - { - "id": "ee4b8908a5b83880", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 13, - "width": 3, - "height": 1, - "name": "rotor_Steps per Rotation", - "label": "Steps per Rotation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 810, - "y": 2180, - "wires": [] - }, - { - "id": "c4deaa38c1b0adbf", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 17, - "width": 3, - "height": 1, - "name": "rotor Acc", - "label": "Acceleration", - "format": "", - "layout": "row-left", - "className": "", - "x": 760, - "y": 2100, - "wires": [] - }, - { - "id": "baec873a95fff48a", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 15, - "width": 3, - "height": 1, - "name": "rotor_delay", - "label": "Delay", - "format": "", - "layout": "row-left", - "className": "", - "x": 770, - "y": 2060, - "wires": [] - }, - { - "id": "355e89ab4e5484e4", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 26, - "width": 6, - "height": 1, - "name": "tt", - "label": "TURNTABLE", - "format": "", - "layout": "row-center", - "className": "", - "x": 90, - "y": 2300, - "wires": [] - }, - { - "id": "10687d331a732790", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_acc", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 32, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0.1", - "max": "2", - "step": "0.1", - "className": "", - "x": 410, - "y": 2380, - "wires": [ - [ - "af88b9da72917d62" - ] - ] - }, - { - "id": "721b9680a3fa460e", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_accramp", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 34, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "500", - "step": "1", - "className": "", - "x": 430, - "y": 2420, - "wires": [ - [ - "b1b4678827d3a6dd" - ] - ] - }, - { - "id": "c6642c7470d3820c", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "tt_stepsperrotation", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 28, - "width": 3, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 450, - "y": 2300, - "wires": [ - [ - "eef89545ec0f6aa8" - ] - ] - }, - { - "id": "18e5918748660109", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 33, - "width": 3, - "height": 1, - "name": "ttAccramp", - "label": "Acceleration ramp", - "format": "", - "layout": "row-left", - "className": "", - "x": 760, - "y": 2420, - "wires": [] - }, - { - "id": "8e805244dc1899e8", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 27, - "width": 3, - "height": 1, - "name": "tt_steps per Rotation", - "label": "Steps per Rotation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 800, - "y": 2300, - "wires": [] - }, - { - "id": "a09e5fbea861bfb1", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 31, - "width": 3, - "height": 1, - "name": "tt Acc", - "label": "Acceleration", - "format": "", - "layout": "row-left", - "className": "", - "x": 750, - "y": 2380, - "wires": [] - }, - { - "id": "7b06448b3b222011", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 29, - "width": 3, - "height": 1, - "name": "tt_delay", - "label": "Delay", - "format": "", - "layout": "row-left", - "className": "", - "x": 760, - "y": 2340, - "wires": [] - }, - { - "id": "0dfc86d90258f9bb", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_angle", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 22, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "180", - "step": "1", - "className": "", - "x": 430, - "y": 2220, - "wires": [ - [ - "c4b5a38c5c1df3d2" - ] - ] - }, - { - "id": "9319d7d4f34c6d22", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 21, - "width": 3, - "height": 1, - "name": "rotor_angle", - "label": "Manual angle", - "format": "", - "layout": "row-spread", - "className": "", - "x": 770, - "y": 2220, - "wires": [] - }, - { - "id": "1610895f430b9aca", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_angle", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 36, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "180", - "step": "1", - "className": "", - "x": 420, - "y": 2460, - "wires": [ - [ - "0f3367983bb8e159" - ] - ] - }, - { - "id": "96a9febc0928b6f0", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 35, - "width": 3, - "height": 1, - "name": "tt_angle", - "label": "Manual angle", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2460, - "wires": [] - }, - { - "id": "e2c5ea8c16a5ea32", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 2, - "width": 6, - "height": 1, - "name": "rotor", - "label": "ROTOR", - "format": "", - "layout": "row-center", - "className": "", - "x": 90, - "y": 1820, - "wires": [] - }, - { - "id": "277037c4716d85bf", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "tt_dir", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 38, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "1", - "className": "", - "x": 410, - "y": 2500, - "wires": [ - [ - "c9d2e31514def4fc" - ] - ] - }, - { - "id": "1361134e9847f003", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_dir", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 24, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "1", - "className": "", - "x": 420, - "y": 2260, - "wires": [ - [ - "523717b0f218a5fd" - ] - ] - }, - { - "id": "6b0d58943ecb8bb2", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 37, - "width": 3, - "height": 1, - "name": "tt_dir", - "label": "Direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2500, - "wires": [] - }, - { - "id": "08f93dd2aeedb391", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 23, - "width": 3, - "height": 1, - "name": "rotor_dir", - "label": "Direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2260, - "wires": [] - }, - { - "id": "46b91bef44714366", - "type": "link in", - "z": "e43a27722b508115", - "name": "advanced settings", - "links": [ - "8750ad979e9ea246" - ], - "x": 95, - "y": 100, - "wires": [ - [ - "89eedf29b404f750" - ] - ] - }, - { - "id": "8750ad979e9ea246", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "mode": "link", - "links": [ - "46b91bef44714366" - ], - "x": 955, - "y": 480, - "wires": [] - }, - { - "id": "2522f888dc58972f", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_delay_before", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 7, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "0.02", - "className": "", - "x": 430, - "y": 2600, - "wires": [ - [ - "5c752757090c49d2" - ] - ] - }, - { - "id": "30e8df3d616512d8", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_gain", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 11, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "10", - "step": "0.1", - "className": "", - "x": 400, - "y": 2640, - "wires": [ - [ - "a1769f0277834f6d" - ] - ] - }, - { - "id": "d855d926df89d65b", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_contrast", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 13, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 420, - "y": 2760, - "wires": [ - [ - "1a8b0ba21b4f3005", - "654bc70a18820828" - ] - ] - }, - { - "id": "7617517dc8ba2859", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_saturation", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 15, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "5", - "step": "0.1", - "className": "", - "x": 420, - "y": 2800, - "wires": [ - [ - "dc8fc962ff7d594b", - "e64feb03a791ca33" - ] - ] - }, - { - "id": "cbaa23c34e10fae1", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_jpeg_q", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 3, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "1", - "max": "100", - "step": "1", - "className": "", - "x": 410, - "y": 2840, - "wires": [ - [ - "00e7836ccb3c4d0c" - ] - ] - }, - { - "id": "bbe443b039a14e21", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 6, - "width": 3, - "height": 1, - "name": "delay_before", - "label": "Delay before", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2600, - "wires": [] - }, - { - "id": "d320ed3d701e6cc2", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 10, - "width": 3, - "height": 1, - "name": "gain", - "label": "Gain", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 2640, - "wires": [] - }, - { - "id": "f5834dd4646c8af9", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 12, - "width": 3, - "height": 1, - "name": "contrast", - "label": "Contrast", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2760, - "wires": [] - }, - { - "id": "ae9a4e19469813ef", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 14, - "width": 3, - "height": 1, - "name": "saturation", - "label": "Saturation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2800, - "wires": [] - }, - { - "id": "bd629d0d31233c8b", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 2, - "width": 3, - "height": 1, - "name": "jpegQ", - "label": "Jpeg Quality", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 2840, - "wires": [] - }, - { - "id": "e89f61dbe6a6cffe", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "ext", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 3, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3000, - "wires": [ - [ - "885bc559fafec5f2" - ] - ] - }, - { - "id": "ece38cb172a12d75", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 2, - "width": 4, - "height": 1, - "name": "ext", - "label": "External Camera", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3000, - "wires": [] - }, - { - "id": "70014da0b6ab6698", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "light1", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 5, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3040, - "wires": [ - [ - "f70321c96bf81360" - ] - ] - }, - { - "id": "29634ea5f6d666df", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 4, - "width": 4, - "height": 1, - "name": "light1", - "label": "Light 1", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3040, - "wires": [] - }, - { - "id": "2544963852c6881a", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "light2", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 7, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3080, - "wires": [ - [ - "95e1603bbd06a69d" - ] - ] - }, - { - "id": "27903533cd85a59e", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 6, - "width": 4, - "height": 1, - "name": "light2", - "label": "Light 2", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3080, - "wires": [] - }, - { - "id": "a1394401246eb735", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotordir", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 9, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3120, - "wires": [ - [ - "a8f92ea6bf394640" - ] - ] - }, - { - "id": "bc0aa4bacdfa94ea", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 8, - "width": 4, - "height": 1, - "name": "rotordir", - "label": "Rotor direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3120, - "wires": [] - }, - { - "id": "f15ca4518b5f223e", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotorstep", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 11, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3160, - "wires": [ - [ - "06397bb46b3bb541" - ] - ] - }, - { - "id": "0d2924b160e7e383", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 10, - "width": 4, - "height": 1, - "name": "rotorstep", - "label": "Rotor step", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3160, - "wires": [] - }, - { - "id": "49900bb9047dd965", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "rotoren", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 13, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3200, - "wires": [ - [ - "687dcdc1ede11700" - ] - ] - }, - { - "id": "a4d743ca73ee1622", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 12, - "width": 4, - "height": 1, - "name": "rotoren", - "label": "Rotor enable", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3200, - "wires": [] - }, - { - "id": "5a90224dc998b417", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "ttdir", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 15, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3240, - "wires": [ - [ - "e220740c0d38ccb0" - ] - ] - }, - { - "id": "67dc1b544c4ddf9f", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 14, - "width": 4, - "height": 1, - "name": "ttdir", - "label": "Turntable direction", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3240, - "wires": [] - }, - { - "id": "d2364ab09627fe94", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "ttstep", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 17, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 390, - "y": 3280, - "wires": [ - [ - "79d7e5a705ab813a" - ] - ] - }, - { - "id": "145b67ac40721ba6", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 16, - "width": 4, - "height": 1, - "name": "ttstep", - "label": "Turntable step", - "format": "", - "layout": "row-spread", - "className": "", - "x": 730, - "y": 3280, - "wires": [] - }, - { - "id": "eef25405472acfee", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "endstop1", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 19, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3320, - "wires": [ - [ - "12d20f2274bcc511" - ] - ] - }, - { - "id": "35eb252a41413531", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 18, - "width": 4, - "height": 1, - "name": "endstop1", - "label": "Endstop Rotor", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3320, - "wires": [] - }, - { - "id": "74e455136b5ca5dd", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "endstop2", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 21, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3360, - "wires": [ - [ - "a4a89668ce4c9f05" - ] - ] - }, - { - "id": "3a74f653800eb831", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 20, - "width": 4, - "height": 1, - "name": "endstop2", - "label": "Endstop Turntable", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3360, - "wires": [] - }, - { - "id": "5fcef1cb2e9e4788", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "Cancel", - "raw": true, - "className": "", - "topic": "", - "name": "confirm", - "x": 680, - "y": 480, - "wires": [ - [ - "29745a36fc157f3f" - ] - ] - }, - { - "id": "f06a7bcad524e9f9", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "msg", - "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", - "outputs": 1, - "x": 530, - "y": 480, - "wires": [ - [ - "5fcef1cb2e9e4788" - ] - ] - }, - { - "id": "f455fb39039617ae", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_rotation", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 5, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "270", - "step": "90", - "className": "", - "x": 410, - "y": 2880, - "wires": [ - [ - "3019576de193d9d6" - ] - ] - }, - { - "id": "fdfbc900fe424eb9", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 4, - "width": 3, - "height": 1, - "name": "cam_rot", - "label": "Image Rotation", - "format": "", - "layout": "row-spread", - "className": "", - "x": 750, - "y": 2880, - "wires": [] - }, - { - "id": "c3699d6b9664ccca", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2060, - "wires": [ - [ - "dfdebe10dbf0e198" - ] - ] - }, - { - "id": "78e256083f59f66f", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2060, - "wires": [ - [] - ] - }, - { - "id": "0f9141b401322374", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2180, - "wires": [ - [ - "9a56c087d941f1da" - ] - ] - }, - { - "id": "29f576be9e292232", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2180, - "wires": [ - [] - ] - }, - { - "id": "23e3099b34c4e475", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2220, - "wires": [ - [ - "0dfc86d90258f9bb" - ] - ] - }, - { - "id": "c4b5a38c5c1df3d2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2220, - "wires": [ - [] - ] - }, - { - "id": "79a14162ac805fac", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2260, - "wires": [ - [ - "1361134e9847f003" - ] - ] - }, - { - "id": "523717b0f218a5fd", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2260, - "wires": [ - [] - ] - }, - { - "id": "f5cf780f3fa8997e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2100, - "wires": [ - [ - "b03e8b51187e88eb" - ] - ] - }, - { - "id": "11fd3363416433f9", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2100, - "wires": [ - [] - ] - }, - { - "id": "02060b3f3b294563", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2140, - "wires": [ - [ - "543e1690693acbeb" - ] - ] - }, - { - "id": "e8b24efb0f30288e", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2140, - "wires": [ - [] - ] - }, - { - "id": "de1ad8b27b72a5ac", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", - "outputs": 1, - "noerr": 4, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2300, - "wires": [ - [ - "c6642c7470d3820c" - ] - ] - }, - { - "id": "ed4d587cb4feb064", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2420, - "wires": [ - [ - "721b9680a3fa460e" - ] - ] - }, - { - "id": "5b02160c33605ae7", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2460, - "wires": [ - [ - "1610895f430b9aca" - ] - ] - }, - { - "id": "304c135ec09801e3", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2500, - "wires": [ - [ - "277037c4716d85bf" - ] - ] - }, - { - "id": "a91dcbe0f9a2416a", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2340, - "wires": [ - [ - "6aae9d4fddf08cc0" - ] - ] - }, - { - "id": "6b2eb1cb95e573f9", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2380, - "wires": [ - [ - "10687d331a732790" - ] - ] - }, - { - "id": "eef89545ec0f6aa8", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2300, - "wires": [ - [] - ] - }, - { - "id": "b1b4678827d3a6dd", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2420, - "wires": [ - [] - ] - }, - { - "id": "0f3367983bb8e159", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2460, - "wires": [ - [] - ] - }, - { - "id": "c9d2e31514def4fc", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2500, - "wires": [ - [] - ] - }, - { - "id": "e50492d1e18f43c6", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2340, - "wires": [ - [] - ] - }, - { - "id": "af88b9da72917d62", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2380, - "wires": [ - [] - ] - }, - { - "id": "43fe948b3e7234e2", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2600, - "wires": [ - [ - "2522f888dc58972f" - ] - ] - }, - { - "id": "5c752757090c49d2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2600, - "wires": [ - [] - ] - }, - { - "id": "435681b3f7625a7e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2640, - "wires": [ - [ - "30e8df3d616512d8" - ] - ] - }, - { - "id": "a1769f0277834f6d", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2640, - "wires": [ - [] - ] - }, - { - "id": "1de07c7d285cbaf3", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2760, - "wires": [ - [ - "d855d926df89d65b" - ] - ] - }, - { - "id": "1a8b0ba21b4f3005", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2760, - "wires": [ - [] - ] - }, - { - "id": "ebc9e283468eda31", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2800, - "wires": [ - [ - "7617517dc8ba2859" - ] - ] - }, - { - "id": "dc8fc962ff7d594b", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2800, - "wires": [ - [] - ] - }, - { - "id": "60d641613527c736", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2840, - "wires": [ - [ - "cbaa23c34e10fae1" - ] - ] - }, - { - "id": "00e7836ccb3c4d0c", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2840, - "wires": [ - [] - ] - }, - { - "id": "7f24c0c34a88ba04", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2880, - "wires": [ - [ - "f455fb39039617ae" - ] - ] - }, - { - "id": "3019576de193d9d6", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2880, - "wires": [ - [] - ] - }, - { - "id": "77bb7dc529d63a7e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3000, - "wires": [ - [ - "e89f61dbe6a6cffe" - ] - ] - }, - { - "id": "885bc559fafec5f2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3000, - "wires": [ - [] - ] - }, - { - "id": "cc6dabe017a9c8a8", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3320, - "wires": [ - [ - "eef25405472acfee" - ] - ] - }, - { - "id": "12d20f2274bcc511", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3320, - "wires": [ - [] - ] - }, - { - "id": "dcb9fed8122759fd", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3040, - "wires": [ - [ - "70014da0b6ab6698" - ] - ] - }, - { - "id": "f70321c96bf81360", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3040, - "wires": [ - [] - ] - }, - { - "id": "013d2057c2347a62", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3080, - "wires": [ - [ - "2544963852c6881a" - ] - ] - }, - { - "id": "95e1603bbd06a69d", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3080, - "wires": [ - [] - ] - }, - { - "id": "f88bbf11d5aa9a14", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3120, - "wires": [ - [ - "a1394401246eb735" - ] - ] - }, - { - "id": "a8f92ea6bf394640", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3120, - "wires": [ - [] - ] - }, - { - "id": "301af70731e096e5", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3160, - "wires": [ - [ - "f15ca4518b5f223e" - ] - ] - }, - { - "id": "06397bb46b3bb541", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3160, - "wires": [ - [] - ] - }, - { - "id": "0456a9ec4c236c9e", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3200, - "wires": [ - [ - "49900bb9047dd965" - ] - ] - }, - { - "id": "687dcdc1ede11700", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3200, - "wires": [ - [] - ] - }, - { - "id": "09d37ba08ec0f163", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3240, - "wires": [ - [ - "5a90224dc998b417" - ] - ] - }, - { - "id": "37d954a4cf7e87ea", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3280, - "wires": [ - [ - "d2364ab09627fe94" - ] - ] - }, - { - "id": "e220740c0d38ccb0", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3240, - "wires": [ - [] - ] - }, - { - "id": "79d7e5a705ab813a", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3280, - "wires": [ - [] - ] - }, - { - "id": "21dc963d967d9c99", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3360, - "wires": [ - [ - "74e455136b5ca5dd" - ] - ] - }, - { - "id": "a4a89668ce4c9f05", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3360, - "wires": [ - [] - ] - }, - { - "id": "22ef66b0e2058be2", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 360, - "wires": [ - [ - "cb3437ec113e1b6f" - ] - ] - }, - { - "id": "9ce01c8ba97932c1", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 400, - "wires": [ - [ - "60fd0adce1cfeb82" - ] - ] - }, - { - "id": "81356177176eebcf", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 480, - "wires": [ - [ - "f6d6cc35679ede63" - ] - ] - }, - { - "id": "b78346ca3ce70c68", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 320, - "wires": [ - [ - "f0d8dbcca76a1926" - ] - ] - }, - { - "id": "e95b86cbac1b03b9", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "3e4c15d7b538f816", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 670, - "y": 1540, - "wires": [ - [ - "3bf622f344172721" - ] - ] - }, - { - "id": "0f0871baf322b6d0", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1820, - "wires": [ - [ - "6ebd15c61a5ca891" - ] - ] - }, - { - "id": "f21a95a732fadae6", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 5, - "width": 3, - "height": 1, - "name": "rotor_anglemin", - "label": "Min Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1820, - "wires": [] - }, - { - "id": "acd10a4c99ee8063", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1820, - "wires": [ - [] - ] - }, - { - "id": "6ebd15c61a5ca891", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_anglemin", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 6, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", - "className": "", - "x": 440, - "y": 1820, - "wires": [ - [ - "acd10a4c99ee8063" - ] - ] - }, - { - "id": "3ad0f0f206e4a873", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_anglemax", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 8, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", - "className": "", - "x": 440, - "y": 1860, - "wires": [ - [ - "031d7697768d0e77" - ] - ] - }, - { - "id": "3b6d759ed5be647f", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "rotor_anglestart", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 4, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "5", - "className": "", - "x": 440, - "y": 1900, - "wires": [ - [ - "be1954dd71d2c94c" - ] - ] - }, - { - "id": "edb1c8fae8b65c82", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1860, - "wires": [ - [ - "3ad0f0f206e4a873" - ] - ] - }, - { - "id": "031d7697768d0e77", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1860, - "wires": [ - [] - ] - }, - { - "id": "462a8f3ca75fc3c8", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1900, - "wires": [ - [ - "3b6d759ed5be647f" - ] - ] - }, - { - "id": "be1954dd71d2c94c", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1900, - "wires": [ - [] - ] - }, - { - "id": "3d7379753d2eda25", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 7, - "width": 3, - "height": 1, - "name": "rotor_anglemax", - "label": "Max Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1860, - "wires": [] - }, - { - "id": "9cc86d1bcae3ab4e", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 3, - "width": 3, - "height": 1, - "name": "rotor_anglestart", - "label": "Start Angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 1900, - "wires": [] - }, - { - "id": "2e9b29c70969cf01", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 135, - "y": 360, - "wires": [ - [ - "22ef66b0e2058be2", - "9ce01c8ba97932c1", - "81356177176eebcf", - "d54b85891248ba88" - ] - ] - }, - { - "id": "592ec13d8f8923a9", - "type": "link in", - "z": "e43a27722b508115", - "name": "ip address", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc", - "eb1a2387a1eeea76", - "c994c779e4bad800" - ], - "x": 85, - "y": 940, - "wires": [ - [ - "ded3086945a6d4b5", - "6ea3cdab41f20f92" - ] - ] - }, - { - "id": "cb40b9341bd22a28", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 185, - "y": 1820, - "wires": [ - [ - "0f0871baf322b6d0", - "edb1c8fae8b65c82", - "462a8f3ca75fc3c8", - "c3699d6b9664ccca", - "f5cf780f3fa8997e", - "02060b3f3b294563", - "0f9141b401322374", - "23e3099b34c4e475", - "79a14162ac805fac", - "de1ad8b27b72a5ac", - "a91dcbe0f9a2416a", - "6b2eb1cb95e573f9", - "ed4d587cb4feb064", - "5b02160c33605ae7", - "304c135ec09801e3", - "f036424d79645761", - "b7db72b7f0599ebd" - ] - ] - }, - { - "id": "d1efcd5fa9d25785", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 155, - "y": 2540, - "wires": [ - [ - "43fe948b3e7234e2", - "435681b3f7625a7e", - "1de07c7d285cbaf3", - "ebc9e283468eda31", - "60d641613527c736", - "7f24c0c34a88ba04", - "6281b2e6e081104d" - ] - ] - }, - { - "id": "da61581182b7299e", - "type": "link in", - "z": "e43a27722b508115", - "name": "enable projectname", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 135, - "y": 3000, - "wires": [ - [ - "77bb7dc529d63a7e", - "dcb9fed8122759fd", - "013d2057c2347a62", - "f88bbf11d5aa9a14", - "301af70731e096e5", - "0456a9ec4c236c9e", - "09d37ba08ec0f163", - "37d954a4cf7e87ea", - "cc6dabe017a9c8a8", - "21dc963d967d9c99" - ] - ] - }, - { - "id": "7e1c84ec516ad0a6", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Reset default", - "group": "4390b2ebcbbe104c", - "order": 6, - "width": 6, - "height": 1, - "passthru": false, - "label": "Restore default settings", - "tooltip": "", - "color": "red", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "This can not be undone!", - "payloadType": "str", - "topic": "Restore default settings?", - "topicType": "str", - "x": 110, - "y": 620, - "wires": [ - [ - "53e6681d7254d484" - ] - ] - }, - { - "id": "53e6681d7254d484", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "No", - "cancel": "Yes", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 270, - "y": 620, - "wires": [ - [ - "c11e79cfa7bc10b7" - ] - ] - }, - { - "id": "c11e79cfa7bc10b7", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 620, - "wires": [ - [ - "307782d10c1acdaf" - ] - ] - }, - { - "id": "307782d10c1acdaf", - "type": "link out", - "z": "e43a27722b508115", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 505, - "y": 620, - "wires": [] - }, - { - "id": "5fff689f9f8bc1ca", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": true, - "className": "", - "topic": "", - "name": "Info", - "x": 1010, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "cca3300a8f0daf4d", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Update&Info", - "group": "ddbd496e.93a288", - "order": 1, - "width": 6, - "height": 1, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 750, - "y": 180, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, - { - "id": "654bc70a18820828", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_contrast?contrast=\" + str(msg['payload']))", - "outputs": 1, - "x": 660, - "y": 2720, - "wires": [ - [] - ] - }, - { - "id": "e64feb03a791ca33", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import camera\n\ncamera(\"/picam2_saturation?saturation=\" + str(msg['payload']))", - "outputs": 1, - "x": 660, - "y": 2680, - "wires": [ - [] - ] - }, - { - "id": "81bd4381cd029958", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "cam_delay_after", - "label": "", - "tooltip": "", - "group": "d324f0b852c2df0a", - "order": 9, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "0", - "max": "1", - "step": "0.02", - "className": "", - "x": 440, - "y": 2560, - "wires": [ - [ - "e612073aded01a8f" - ] - ] - }, - { - "id": "0d92559980944ae3", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "d324f0b852c2df0a", - "order": 8, - "width": 3, - "height": 1, - "name": "delay_after", - "label": "Delay after", - "format": "", - "layout": "row-spread", - "className": "", - "x": 760, - "y": 2560, - "wires": [] - }, - { - "id": "6281b2e6e081104d", - "type": "function", - "z": "e43a27722b508115", - "name": "loadF", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 2560, - "wires": [ - [ - "81bd4381cd029958" - ] - ] - }, - { - "id": "e612073aded01a8f", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 620, - "y": 2560, - "wires": [ - [] - ] - }, - { - "id": "e2411b49791840e0", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "reboot", - "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", - "outputs": 1, - "x": 270, - "y": 520, - "wires": [ - [] - ] - }, - { - "id": "01c882fcc51b349c", - "type": "link in", - "z": "e43a27722b508115", - "name": "reboot", - "links": [ - "16c76929f88df841", - "fe3a855fee9e28c6", - "09d4a9c756161e10" - ], - "x": 155, - "y": 520, - "wires": [ - [ - "e2411b49791840e0" - ] - ] - }, - { - "id": "e51dd5e5c0f050d6", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "SSID", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 4, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "ssid", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 210, - "y": 980, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "9959649037cb063b", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "Password", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "password", - "delay": "0", - "topic": "password", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 220, - "y": 1020, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "1d42cb9a63409283", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "", - "label": "Country Code 2", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 6, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "country", - "sendOnBlur": true, - "className": "", - "topicType": "str", - "x": 240, - "y": 1060, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "84ecaafd629c0f7a", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "", - "group": "8ab79a98e536e0d6", - "order": 7, - "width": 0, - "height": 0, - "passthru": false, - "label": "Connect to Wifi", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "connect", - "topicType": "str", - "x": 240, - "y": 1100, - "wires": [ - [ - "a7d233f984009e2e" - ] - ] - }, - { - "id": "6ea3cdab41f20f92", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "8ab79a98e536e0d6", - "order": 2, - "width": 0, - "height": 0, - "name": "", - "label": "Hotspot Mode", - "format": "{{msg.mode}}", - "layout": "row-spread", - "className": "", - "x": 240, - "y": 900, - "wires": [] - }, - { - "id": "a7d233f984009e2e", - "type": "function", - "z": "e43a27722b508115", - "name": "function 1", - "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 440, - "y": 980, - "wires": [ - [ - "9b851aa999e86fd7", - "021dc780b478fee6", - "9ec0ad9fd3687e9f" - ] - ] - }, - { - "id": "65518f3d4e3095e5", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 1", - "links": [ - "200d4b9951b6e066" - ], - "x": 85, - "y": 980, - "wires": [ - [ - "e51dd5e5c0f050d6", - "9959649037cb063b", - "1d42cb9a63409283" - ] - ] - }, - { - "id": "9b851aa999e86fd7", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", - "outputs": 1, - "x": 670, - "y": 980, - "wires": [ - [ - "c994c779e4bad800", - "11b19e9c6a4ffd8d", - "36890eb99a2ca1cf" - ] - ] - }, - { - "id": "11b19e9c6a4ffd8d", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 870, - "y": 980, - "wires": [ - [] - ] - }, - { - "id": "021dc780b478fee6", - "type": "debug", - "z": "e43a27722b508115", - "name": "debug 3", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 640, - "y": 920, - "wires": [] - }, - { - "id": "c994c779e4bad800", - "type": "link out", - "z": "e43a27722b508115", - "name": "link out 2", - "mode": "link", - "links": [ - "592ec13d8f8923a9" - ], - "x": 815, - "y": 1020, - "wires": [] - }, - { - "id": "1eef47e0074545a9", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", - "outputs": 2, - "x": 670, - "y": 1100, - "wires": [ - [ - "c994c779e4bad800", - "36890eb99a2ca1cf" - ], - [] - ] - }, - { - "id": "434b04d8a65951ce", - "type": "inject", - "z": "e43a27722b508115", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 440, - "y": 1140, - "wires": [ - [ - "1eef47e0074545a9" - ] - ] - }, - { - "id": "9ec0ad9fd3687e9f", - "type": "ui_toast", - "z": "e43a27722b508115", - "position": "bottom right", - "displayTime": "5", - "highlight": "", - "sendall": true, - "outputs": 0, - "ok": "OK", - "cancel": "", - "raw": false, - "className": "", - "topic": "Adding new Wifi", - "name": "", - "x": 670, - "y": 1020, - "wires": [] - }, - { - "id": "36890eb99a2ca1cf", - "type": "debug", - "z": "e43a27722b508115", - "name": "debug 4", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 860, - "y": 940, - "wires": [] - }, - { - "id": "6b7245c3dcb694c8", - "type": "ui_slider", - "z": "e43a27722b508115", - "name": "endstop_angle", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 12, - "width": 3, - "height": 1, - "passthru": false, - "outs": "end", - "topic": "", - "topicType": "str", - "min": "-90", - "max": "90", - "step": "1", - "className": "", - "x": 440, - "y": 2020, - "wires": [ - [ - "85ad07b8f973bbe2" - ] - ] - }, - { - "id": "69516440e3997111", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 11, - "width": 3, - "height": 1, - "name": "endstop_angle", - "label": "Endstop angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 780, - "y": 2020, - "wires": [] - }, - { - "id": "85ad07b8f973bbe2", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 2020, - "wires": [ - [] - ] - }, - { - "id": "f036424d79645761", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 2020, - "wires": [ - [ - "6b7245c3dcb694c8" - ] - ] - }, - { - "id": "253feafa5a2f8b1d", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "rotor_enable_endstop", - "label": "", - "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 10, - "width": 3, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 460, - "y": 1940, - "wires": [ - [ - "1916dc3fd04f0664", - "6cb92b9b9f0d6954" - ] - ] - }, - { - "id": "b7db72b7f0599ebd", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 1940, - "wires": [ - [ - "253feafa5a2f8b1d" - ] - ] - }, - { - "id": "1916dc3fd04f0664", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 1940, - "wires": [ - [] - ] - }, - { - "id": "de409e57a0c4bf41", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 9, - "width": 3, - "height": 1, - "name": "rotor_enable_endstop", - "label": "Enable Endstop", - "format": "", - "layout": "row-left", - "className": "", - "x": 800, - "y": 1940, - "wires": [] - }, - { - "id": "6cb92b9b9f0d6954", - "type": "function", - "z": "e43a27722b508115", - "name": "msg", - "func": "msg.enabled = msg.payload\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 1980, - "wires": [ - [ - "69516440e3997111", - "f036424d79645761" - ] - ] - }, - { - "id": "d54b85891248ba88", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 440, - "wires": [ - [ - "eefed04c25e3e4d6" - ] - ] - }, - { - "id": "eefed04c25e3e4d6", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "", - "label": "Group Stack Photos", - "tooltip": "Group photos that are part of the same focus photoset", - "group": "d324f0b852c2df0a", - "order": 1, - "width": "6", - "height": "1", - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 440, - "y": 440, - "wires": [ - [ - "2aaf7c7f0f0c146f" - ] - ] - }, - { - "id": "2aaf7c7f0f0c146f", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "group_stack_photos", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", - "outputs": 1, - "x": 660, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "84a1d063a2a2b018", - "type": "comment", - "z": "e43a27722b508115", - "name": "Messaging", - "info": "", - "x": 100, - "y": 3500, - "wires": [] - }, - { - "id": "a12ead9ccf239c19", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3560, - "wires": [ - [ - "d0a1a4947a1137ca" - ] - ] - }, - { - "id": "9a4c3cbe89994626", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "telegram_enable", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", - "outputs": 1, - "x": 520, - "y": 3560, - "wires": [ - [] - ] - }, - { - "id": "d0a1a4947a1137ca", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "telegram_enable", - "label": "Enable Telegram", - "tooltip": "Enable telegram bot", - "group": "220493325bb79987", - "order": 1, - "width": "6", - "height": "1", - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 340, - "y": 3560, - "wires": [ - [ - "9a4c3cbe89994626" - ] - ] - }, - { - "id": "28eeaa3a8eb77679", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "telegram_api_token", - "label": "Telegram Api Token", - "tooltip": "telegram api token", - "group": "220493325bb79987", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "password", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 350, - "y": 3600, - "wires": [ - [ - "1c08a329bd2a669c" - ] - ] - }, - { - "id": "bf8e971a52cddab1", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3600, - "wires": [ - [ - "28eeaa3a8eb77679" - ] - ] - }, - { - "id": "1c08a329bd2a669c", - "type": "function", - "z": "e43a27722b508115", - "name": "telegram_api_token", - "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 3600, - "wires": [ - [] - ] - }, - { - "id": "a26c0482377667c9", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "telegram_client_id", - "label": "Telegram Client Id", - "tooltip": "The Id of the user or channel to send the message to", - "group": "220493325bb79987", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "text", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 350, - "y": 3640, - "wires": [ - [ - "b5aba11033c5f952" - ] - ] - }, - { - "id": "058743d0e5afb87b", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3640, - "wires": [ - [ - "a26c0482377667c9" - ] - ] - }, - { - "id": "b5aba11033c5f952", - "type": "function", - "z": "e43a27722b508115", - "name": "telegram_client_id", - "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 3640, - "wires": [ - [] - ] - }, -{ - "id": "c59e7b205d80fe0a", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "Messaging", - "group": "220493325bb79987", - "order": 1, - "width": 0, - "height": 0, - "passthru": false, - "label": "", - "tooltip": "", - "color": "", - "bgcolor": "transparent", - "className": "", - "icon": "fa-question-circle", - "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 770, - "y": 300, - "wires": [ - [ - "5fff689f9f8bc1ca" - ] - ] - }, -{ - "id": "2afb6a45c73fa244", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 2", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3600, - "wires": [ - [ - "a12ead9ccf239c19", - "bf8e971a52cddab1", - "058743d0e5afb87b" - ] - ] - }, -{ - "id": "69885a9ce218eb71", - "type": "comment", - "z": "e43a27722b508115", - "name": "Coloritos", - "info": "", - "x": 100, - "y": 3740, - "wires": [] - }, - { - "id": "dc1cde67c3022e6b", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3800, - "wires": [ - [ - "0dccca85770c7936" - ] - ] - }, - { - "id": "b63e8246ad14ad9d", - "type": "function", - "z": "e43a27722b508115", - "name": "interface-color", - "func": "var file = 'interface_color'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 540, - "y": 3800, - "wires": [ - [] - ] - }, - { - "id": "b7044aa75196b521", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 3", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3800, - "wires": [ - [ - "dc1cde67c3022e6b" - ] - ] - }, - { - "id": "0dccca85770c7936", - "type": "ui_dropdown", - "z": "e43a27722b508115", - "name": "interface_color", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "15edc2ce885dddb3", - "order": 1, - "width": 0, - "height": 0, - "passthru": true, - "multiple": false, - "options": [ - { - "label": "Aburrido", - "value": "#097479", - "type": "str" - }, - { - "label": "Morado", - "value": "#790974", - "type": "str" - }, - { - "label": "Berenjena", - "value": "#79093c", - "type": "str" - }, - { - "label": "Azul", - "value": "#093c79 ", - "type": "str" - }, - { - "label": "Oliva", - "value": "#747909", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 360, - "y": 3800, - "wires": [ - [ - "b63e8246ad14ad9d" - ] - ] - }, -{ - "id": "667950f6671bd1a0", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 840, - "wires": [ - [ - "b82a1cbefad51cd8" - ] - ] - }, - { - "id": "5f32d7e78e368454", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'hostname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 840, - "wires": [ - [] - ] - }, - { - "id": "b82a1cbefad51cd8", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "hostname", - "label": "Hostname", - "tooltip": "", - "group": "8ab79a98e536e0d6", - "order": 1, - "width": 0, - "height": 0, - "passthru": true, - "mode": "text", - "delay": 300, - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 360, - "y": 840, - "wires": [ - [ - "5f32d7e78e368454" - ] - ] - }, -{ - "id": "5fd155711e29b1b8", - "type": "comment", - "z": "e43a27722b508115", - "name": "Monitoring", - "info": "", - "x": 100, - "y": 3860, - "wires": [] - }, - { - "id": "815702499384f118", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'datadog_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3920, - "wires": [ - [ - "bfdbdae28bf42ed4" - ] - ] - }, - { - "id": "464c8495f86daaa7", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "datadog_enable", - "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('datadog_enable'):\n save('datadog_enable', state)\n", - "outputs": 1, - "x": 520, - "y": 3920, - "wires": [ - [] - ] - }, - { - "id": "bfdbdae28bf42ed4", - "type": "ui_switch", - "z": "e43a27722b508115", - "name": "datadog_enable", - "label": "Enable Datadog", - "tooltip": "Enable Datadog monitoring", - "group": "33aff36289823faa", - "order": 1, - "width": "6", - "height": "1", - "passthru": true, - "decouple": "false", - "topic": "topic", - "topicType": "msg", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 340, - "y": 3920, - "wires": [ - [ - "464c8495f86daaa7" - ] - ] - }, - { - "id": "f93ce2d26953341f", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "datadog_api_token", - "label": "Datadog Api Token", - "tooltip": "Datadog Api Token", - "group": "33aff36289823faa", - "order": 5, - "width": 6, - "height": 1, - "passthru": false, - "mode": "password", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 350, - "y": 3960, - "wires": [ - [ - "647641e79884eb87" - ] - ] - }, - { - "id": "ee668e39d213070b", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 190, - "y": 3960, - "wires": [ - [ - "f93ce2d26953341f" - ] - ] - }, - { - "id": "647641e79884eb87", - "type": "function", - "z": "e43a27722b508115", - "name": "datadog_api_token", - "func": "var file = 'datadog_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 3960, - "wires": [ - [] - ] - }, - { - "id": "ff2dea1ab9cb7776", - "type": "link in", - "z": "e43a27722b508115", - "name": "link in 4", - "links": [ - "50eeb3e362f9027f", - "960912e90ba5b5bc" - ], - "x": 65, - "y": 3960, - "wires": [ - [ - "815702499384f118", - "ee668e39d213070b" - ] - ] - }, -{ - "id": "a1b81e7fe94ad4e5", - "type": "python3-function", - "z": "e43a27722b508115", - "name": "", - "func": "import subprocess\nsubprocess.run([\"systemctl\",\"restart\",\"nodered\"])\nreturn msg", - "outputs": 1, - "x": 530, - "y": 3740, - "wires": [ - [] - ] - }, - { - "id": "2f3a3c0e682ae862", - "type": "ui_button", - "z": "e43a27722b508115", - "name": "restart_interface", - "group": "15edc2ce885dddb3", - "order": 1, - "width": 0, - "height": 0, - "passthru": false, - "label": "Restart Interface", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 340, - "y": 3740, - "wires": [ - [ - "a1b81e7fe94ad4e5" - ] - ] - }, - { - "id": "4c7fa5b5b27b83a5", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'meanwhile'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/202411S/update/202411S/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", - "outputs": 1, - "x": 260, - "y": 140, - "wires": [ - [ - "e23c514008cad1a1" - ] - ] - }, - { - "id": "80175eb8dc6ad009", - "type": "inject", - "z": "a5557543ccff5889", - "name": "", - "props": [ - { - "p": "payload" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 100, - "y": 140, - "wires": [ - [ - "4c7fa5b5b27b83a5" - ] - ] - }, - { - "id": "d7362e6e0ec7bdaa", - "type": "inject", - "z": "a5557543ccff5889", - "name": "", - "props": [ - { - "p": "overwrite", - "v": "true", - "vt": "bool" - }, - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 90, - "y": 220, - "wires": [ - [ - "4ce127c61c3c5966", - "beacc3dc5398fa79" - ] - ] - }, - { - "id": "4ce127c61c3c5966", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "prepare image creation", - "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", - "outputs": 1, - "x": 290, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "beacc3dc5398fa79", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "38783aea9cc317a6" - ], - "x": 195, - "y": 260, - "wires": [] - }, - { - "id": "e23c514008cad1a1", - "type": "debug", - "z": "a5557543ccff5889", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 480, - "y": 140, - "wires": [] - }, - { - "id": "b0629875a30ae1d7", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", - "outputs": 2, - "x": 390, - "y": 540, - "wires": [ - [ - "1bbe2d769f42c313" - ], - [ - "fefe45404bdb19c4" - ] - ] - }, - { - "id": "c7b6d05a62172432", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "ddbd496e.93a288", - "order": 3, - "width": 0, - "height": 0, - "name": "", - "label": "Status:", - "format": "{{msg.status}}", - "layout": "row-spread", - "className": "", - "x": 210, - "y": 400, - "wires": [] - }, - { - "id": "fefe45404bdb19c4", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", - "outputs": 1, - "x": 550, - "y": 560, - "wires": [ - [ - "1bbe2d769f42c313", - "ae92a328af306ebb" - ] - ] - }, - { - "id": "d0104e0163745993", - "type": "link in", - "z": "a5557543ccff5889", - "name": "", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 115, - "y": 440, - "wires": [ - [ - "ec30638407332e43", - "38cbf7965d1c1834", - "49f1ecb29a3f84f4" - ] - ] - }, - { - "id": "ec30638407332e43", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadS", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 480, - "wires": [ - [ - "2852023f3aa8db10" - ] - ] - }, - { - "id": "2852023f3aa8db10", - "type": "ui_dropdown", - "z": "a5557543ccff5889", - "name": "", - "label": "", - "tooltip": "", - "place": "Select option", - "group": "ddbd496e.93a288", - "order": 5, - "width": 2, - "height": 1, - "passthru": false, - "multiple": false, - "options": [ - { - "label": "stable", - "value": "stable", - "type": "str" - }, - { - "label": "beta", - "value": "beta", - "type": "str" - }, - { - "label": "meanwhile", - "value": "meanwhile", - "type": "str" - } - ], - "payload": "", - "topic": "topic", - "topicType": "msg", - "className": "", - "x": 340, - "y": 480, - "wires": [ - [ - "1e10b387ee30c486" - ] - ] - }, - { - "id": "1e10b387ee30c486", - "type": "function", - "z": "a5557543ccff5889", - "name": "write", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 470, - "y": 480, - "wires": [ - [] - ] - }, - { - "id": "274129c51b0b87ef", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "ddbd496e.93a288", - "order": 4, - "width": 4, - "height": 1, - "name": "", - "label": "Updatetype: ", - "format": "{{msg.payload}}", - "layout": "row-spread", - "className": "", - "x": 610, - "y": 480, - "wires": [] - }, - { - "id": "51cd8c8643e6b46a", - "type": "ui_switch", - "z": "a5557543ccff5889", - "name": "", - "label": "Auto-check update availability", - "tooltip": "", - "group": "ddbd496e.93a288", - "order": 6, - "width": 6, - "height": 1, - "passthru": true, - "decouple": "false", - "topic": "", - "topicType": "str", - "style": "", - "onvalue": "true", - "onvalueType": "bool", - "onicon": "", - "oncolor": "", - "offvalue": "false", - "offvalueType": "bool", - "officon": "", - "offcolor": "", - "animate": false, - "className": "", - "x": 410, - "y": 440, - "wires": [ - [ - "1ab4c6b4b232a022" - ] - ] - }, - { - "id": "38cbf7965d1c1834", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 440, - "wires": [ - [ - "51cd8c8643e6b46a" - ] - ] - }, - { - "id": "1ab4c6b4b232a022", - "type": "function", - "z": "a5557543ccff5889", - "name": "write", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 610, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "ae92a328af306ebb", - "type": "ui_toast", - "z": "a5557543ccff5889", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "NO", - "cancel": "YES", - "raw": false, - "className": "", - "topic": "", - "name": "", - "x": 710, - "y": 560, - "wires": [ - [ - "2de63e8e3ae5fb0c", - "929281fef53e09f8" - ] - ] - }, - { - "id": "cbd0afc4aa7b302a", - "type": "link in", - "z": "a5557543ccff5889", - "name": "update status", - "links": [ - "1bbe2d769f42c313", - "42061b28cff81f99" - ], - "x": 115, - "y": 400, - "wires": [ - [ - "c7b6d05a62172432", - "c94623ddd9d95f78" - ] - ] - }, - { - "id": "1bbe2d769f42c313", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "cbd0afc4aa7b302a" - ], - "x": 665, - "y": 520, - "wires": [] - }, - { - "id": "7cf60615d93e696b", - "type": "ui_button", - "z": "a5557543ccff5889", - "name": "", - "group": "ddbd496e.93a288", - "order": 7, - "width": 6, - "height": 1, - "passthru": false, - "label": "Check Updates", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 180, - "y": 560, - "wires": [ - [ - "b0629875a30ae1d7" - ] - ] - }, - { - "id": "2de63e8e3ae5fb0c", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/main/update/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", - "outputs": 1, - "x": 880, - "y": 560, - "wires": [ - [ - "42061b28cff81f99", - "fe3a855fee9e28c6" - ] - ] - }, - { - "id": "929281fef53e09f8", - "type": "function", - "z": "a5557543ccff5889", - "name": "msg", - "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 850, - "y": 520, - "wires": [ - [ - "42061b28cff81f99" - ] - ] - }, - { - "id": "42061b28cff81f99", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "cbd0afc4aa7b302a" - ], - "x": 995, - "y": 520, - "wires": [] - }, - { - "id": "49f1ecb29a3f84f4", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 520, - "wires": [ - [ - "b0629875a30ae1d7" - ] - ] - }, - { - "id": "fe3a855fee9e28c6", - "type": "link out", - "z": "a5557543ccff5889", - "name": "", - "mode": "link", - "links": [ - "9bb0adbd716ce347", - "01c882fcc51b349c" - ], - "x": 995, - "y": 560, - "wires": [] - }, - { - "id": "5e7d5e4335d37794", - "type": "link in", - "z": "a5557543ccff5889", - "name": "", - "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" - ], - "x": 95, - "y": 700, - "wires": [ - [ - "2bb5fe78e09fec8a" - ] - ] - }, - { - "id": "2bb5fe78e09fec8a", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "msg", - "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", - "outputs": 1, - "x": 210, - "y": 700, - "wires": [ - [ - "dbc77052ac950624", - "d97c3068ef5fef96", - "73a3b828f862312b", - "901e31453b2bdff8", - "f983854748ee4763", - "5347c7c517f5e8c7", - "3a5016f7003cd72c", - "6d720c4a4ecd9475", - "6438b7d060a70d81" - ] - ] - }, - { - "id": "d97c3068ef5fef96", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 2, - "width": 0, - "height": 0, - "name": "", - "label": "OS:", - "format": "{{msg.os}}", - "layout": "row-spread", - "className": "", - "x": 490, - "y": 740, - "wires": [] - }, - { - "id": "73a3b828f862312b", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 8, - "width": 0, - "height": 0, - "name": "", - "label": "Flask:", - "format": "{{msg.flask}}", - "layout": "row-spread", - "className": "", - "x": 490, - "y": 780, - "wires": [] - }, - { - "id": "dbc77052ac950624", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 1, - "width": 0, - "height": 0, - "name": "", - "label": "Device:", - "format": "{{msg.device}}", - "layout": "row-spread", - "className": "", - "x": 500, - "y": 700, - "wires": [] - }, - { - "id": "3f42560297fe6978", - "type": "ui_template", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "name": "Download LOG", - "order": 10, - "width": 6, - "height": 1, - "format": "\n
Download error log\n
\n", - "storeOutMessages": false, - "fwdInMessages": false, - "resendOnRefresh": false, - "templateScope": "local", - "className": "", - "x": 180, - "y": 1060, - "wires": [ - [] - ] - }, - { - "id": "c94623ddd9d95f78", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "get update", - "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", - "outputs": 1, - "x": 210, - "y": 360, - "wires": [ - [] - ] - }, - { - "id": "39a502b38837273d", - "type": "link in", - "z": "a5557543ccff5889", - "name": "", - "links": [ - "1e7457ea9c2c5e09" - ], - "x": 245, - "y": 600, - "wires": [ - [ - "b0629875a30ae1d7" - ] - ] - }, - { - "id": "901e31453b2bdff8", - "type": "delay", - "z": "a5557543ccff5889", - "name": "", - "pauseType": "delay", - "timeout": "10", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 220, - "y": 740, - "wires": [ - [ - "2bb5fe78e09fec8a" - ] - ] - }, - { - "id": "f983854748ee4763", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 3, - "width": 0, - "height": 0, - "name": "", - "label": "", - "format": "{{msg.osdate}}", - "layout": "row-spread", - "className": "", - "x": 490, - "y": 820, - "wires": [] - }, - { - "id": "5347c7c517f5e8c7", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 4, - "width": 0, - "height": 0, - "name": "", - "label": "CPU temp:", - "format": "{{msg.temp}}", - "layout": "row-spread", - "className": "", - "x": 510, - "y": 860, - "wires": [] - }, - { - "id": "3a5016f7003cd72c", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 5, - "width": 0, - "height": 0, - "name": "", - "label": "CPU memory:", - "format": "{{msg.cpu}}", - "layout": "row-spread", - "className": "", - "x": 520, - "y": 900, - "wires": [] - }, - { - "id": "6d720c4a4ecd9475", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 6, - "width": 0, - "height": 0, - "name": "", - "label": "Swap memory:", - "format": "{{msg.swap}}", - "layout": "row-spread", - "className": "", - "x": 520, - "y": 940, - "wires": [] - }, - { - "id": "6438b7d060a70d81", - "type": "ui_text", - "z": "a5557543ccff5889", - "group": "3ce32450.e0cffc", - "order": 7, - "width": 0, - "height": 0, - "name": "", - "label": "Diskspace:", - "format": "{{msg.diskspace}}", - "layout": "row-spread", - "className": "", - "x": 510, - "y": 980, - "wires": [] - }, - { - "id": "8d012912f302be85", - "type": "ui_button", - "z": "a5557543ccff5889", - "name": "", - "group": "ddbd496e.93a288", - "order": 8, - "width": 6, - "height": 1, - "passthru": false, - "label": "Show Details/Changelog", - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 210, - "y": 640, - "wires": [ - [ - "5242607a723cc628" - ] - ] - }, - { - "id": "5242607a723cc628", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "Changelog", - "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", - "outputs": 1, - "x": 430, - "y": 640, - "wires": [ - [ - "573722197b15bf84" - ] - ] - }, - { - "id": "573722197b15bf84", - "type": "ui_toast", - "z": "a5557543ccff5889", - "position": "dialog", - "displayTime": "3", - "highlight": "", - "sendall": true, - "outputs": 1, - "ok": "OK", - "cancel": "", - "raw": true, - "className": "", - "topic": "", - "name": "", - "x": 610, - "y": 640, - "wires": [ - [] - ] - }, - { - "id": "cde61b7de9eeaba7", - "type": "ui_button", - "z": "a5557543ccff5889", - "name": "", - "group": "3ce32450.e0cffc", - "order": 9, - "width": 0, - "height": 0, - "passthru": false, - "label": "Expand Root", - "tooltip": "Sets the maximum space your SD card admits", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "payload": "expand", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "x": 510, - "y": 1020, - "wires": [ - [ - "eab36487d201f867" - ] - ] - }, - { - "id": "eab36487d201f867", - "type": "python3-function", - "z": "a5557543ccff5889", - "name": "", - "func": "import subprocess\nsubprocess.run([\"raspi-config\",\"--expand-rootfs\"])\nreturn msg", - "outputs": 1, - "x": 690, - "y": 1020, - "wires": [ - [] - ] - } -] \ No newline at end of file diff --git a/update/2024-11S/beta/startup.sh b/update/2024-11S/beta/startup.sh new file mode 100755 index 0000000..bdaad1d --- /dev/null +++ b/update/2024-11S/beta/startup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +settings_folder="/home/pi/OpenScan/settings" + +# Generate an unique UUID that identifies that OpenScan + +if [ ! -f $settings_folder/openscan_uuid ]; then + echo $(cat /proc/sys/kernel/random/uuid) > $settings_folder/openscan_uuid +fi +echo `cat /proc/cpuinfo|grep Model|cut -d: -f2|awk '{$1=$1};1'` > $settings_folder/architecture +echo `libcamera-still --list-cameras|head -3|tail -1|cut -d: -f2|cut -d[ -f1|awk '{$1=$1};1'` > $settings_folder/camera diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index 1ef6eb4..667c23e 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -31,27 +31,42 @@ "1": { "src": "beta/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 13012 + "filesize": 17869 }, "2": { "src": "beta/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10253 + "filesize": 10249 }, "3": { "src": "beta/config.txt", "dst": "/boot/config.txt", - "filesize": 2185 + "filesize": 864 }, "4": { - "src": "beta/flows.json.tmpl", - "dst": "/home/pi/OpenScan/settings/.node-red/flows.json.tmpl", - "filesize": 337413 + "src": "beta/flows.json", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", + "filesize": 333492 }, "5": { "src": "beta/settings.js", "dst": "/root/.node-red/settings.js", "filesize": 21248 + }, + "6": { + "src": "beta/OpenScanStatistics.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", + "filesize": 793 + }, + "7": { + "src": "beta/expand_root.sh", + "dst": "/home/pi/OpensScan/files/expand_root.sh", + "filesize": 170 + }, + "8": { + "src": "beta/startup.sh", + "dst": "/home/pi/OpensScan/files/startup.sh", + "filesize": 460 } }, "meanwhile": { From f0759881860c9528f1ace09fac24b39022f32742 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 28 Sep 2024 13:06:09 +0200 Subject: [PATCH 14/38] Shield choice on meanwhile --- update/2024-11S/meanwhile/flows.json | 259 ++++++++++++++++----------- update/2024-11S/update.json | 2 +- 2 files changed, 153 insertions(+), 108 deletions(-) diff --git a/update/2024-11S/meanwhile/flows.json b/update/2024-11S/meanwhile/flows.json index 6fa42e1..bf7892f 100644 --- a/update/2024-11S/meanwhile/flows.json +++ b/update/2024-11S/meanwhile/flows.json @@ -173,7 +173,7 @@ "link": "https://openscan-org.github.io/OpenScan-Doc/", "icon": "fa-bookmark", "target": "iframe", - "order": 6 + "order": 8 }, { "id": "23f75a8768250ce8", @@ -182,7 +182,7 @@ "link": "https://www.patreon.com/OpenScan", "icon": "fa-bookmark", "target": "newtab", - "order": 5 + "order": 7 }, { "id": "b5fdd57b.15eda8", @@ -312,7 +312,7 @@ "type": "ui_group", "name": "Network", "tab": "457102eadc9ddb6c", - "order": 2, + "order": 4, "disp": true, "width": "6", "collapse": true, @@ -323,7 +323,7 @@ "type": "ui_group", "name": "Pinout", "tab": "457102eadc9ddb6c", - "order": 6, + "order": 3, "disp": true, "width": "6", "collapse": true, @@ -334,7 +334,7 @@ "type": "ui_group", "name": "Motor", "tab": "457102eadc9ddb6c", - "order": 5, + "order": 7, "disp": true, "width": "6", "collapse": true, @@ -345,7 +345,7 @@ "type": "ui_group", "name": "Camera", "tab": "457102eadc9ddb6c", - "order": 4, + "order": 6, "disp": true, "width": "6", "collapse": true, @@ -356,7 +356,7 @@ "type": "ui_group", "name": "OpenScanCloud", "tab": "457102eadc9ddb6c", - "order": 3, + "order": 5, "disp": true, "width": "6", "collapse": false, @@ -367,7 +367,7 @@ "type": "ui_tab", "name": "Settings", "icon": "dashboard", - "order": 4, + "order": 5, "disabled": false, "hidden": false }, @@ -486,7 +486,7 @@ "type": "ui_group", "name": "Messaging", "tab": "457102eadc9ddb6c", - "order": 7, + "order": 8, "disp": true, "width": "6", "collapse": false, @@ -495,7 +495,7 @@ { "id": "ac59b8fb186de073", "type": "ui_group", - "name": "[Statistics]", + "name": "Statistics", "tab": "656b4eb8b15dab8f", "order": 3, "disp": true, @@ -508,9 +508,21 @@ "type": "ui_tab", "name": "Statistics", "icon": "dashboard", + "order": 6, "disabled": false, "hidden": false }, + { + "id": "0b244f698c7ac9a2", + "type": "ui_group", + "name": "Shield Type", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -664,7 +676,8 @@ "d1efcd5fa9d25785", "da61581182b7299e", "2afb6a45c73fa244", - "9b3e6a06c82a0f52" + "9b3e6a06c82a0f52", + "fbc5fc2e65311f8b" ], "x": 645, "y": 60, @@ -785,9 +798,9 @@ "name": "enable", "mode": "link", "links": [ + "65518f3d4e3095e5", "8367cfa0bf5bc5df", - "c8b93b42c720b9cf", - "65518f3d4e3095e5" + "c8b93b42c720b9cf" ], "x": 345, "y": 280, @@ -1146,7 +1159,8 @@ "d1efcd5fa9d25785", "da61581182b7299e", "2afb6a45c73fa244", - "9b3e6a06c82a0f52" + "9b3e6a06c82a0f52", + "fbc5fc2e65311f8b" ], "x": 1015, "y": 540, @@ -2195,7 +2209,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = \"green\"\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n \ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -5907,49 +5921,6 @@ "y": 3320, "wires": [] }, - { - "id": "74e455136b5ca5dd", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "endstop2", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 21, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3360, - "wires": [ - [ - "a4a89668ce4c9f05" - ] - ] - }, - { - "id": "3a74f653800eb831", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 20, - "width": 4, - "height": 1, - "name": "endstop2", - "label": "Endstop Turntable", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3360, - "wires": [] - }, { "id": "5fcef1cb2e9e4788", "type": "ui_toast", @@ -7004,42 +6975,6 @@ [] ] }, - { - "id": "21dc963d967d9c99", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3360, - "wires": [ - [ - "74e455136b5ca5dd" - ] - ] - }, - { - "id": "a4a89668ce4c9f05", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3360, - "wires": [ - [] - ] - }, { "id": "22ef66b0e2058be2", "type": "function", @@ -7510,8 +7445,7 @@ "0456a9ec4c236c9e", "09d37ba08ec0f163", "37d954a4cf7e87ea", - "cc6dabe017a9c8a8", - "21dc963d967d9c99" + "cc6dabe017a9c8a8" ] ] }, @@ -8275,7 +8209,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 410, + "x": 450, "y": 1980, "wires": [ [ @@ -8386,7 +8320,7 @@ "name": "telegram_enable", "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", "outputs": 1, - "x": 520, + "x": 540, "y": 3560, "wires": [ [] @@ -8663,6 +8597,107 @@ ] ] }, + { + "id": "48386fdb54980ec7", + "type": "comment", + "z": "e43a27722b508115", + "name": "Shield", + "info": "", + "x": 90, + "y": 3760, + "wires": [] + }, + { + "id": "fbc5fc2e65311f8b", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3840, + "wires": [ + [ + "5618e266f6966ae6" + ] + ] + }, + { + "id": "5618e266f6966ae6", + "type": "function", + "z": "e43a27722b508115", + "name": "loadl", + "func": "var file = 'shield_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 170, + "y": 3840, + "wires": [ + [ + "c97113d841391e40" + ] + ] + }, + { + "id": "c97113d841391e40", + "type": "ui_dropdown", + "z": "e43a27722b508115", + "name": "", + "label": "Shield Type", + "tooltip": "", + "place": "Select option", + "group": "0b244f698c7ac9a2", + "order": 0, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "Green", + "value": "green", + "type": "str" + }, + { + "label": "Black", + "value": "black", + "type": "str" + } + ], + "payload": "", + "topic": "payload", + "topicType": "msg", + "className": "", + "x": 310, + "y": 3840, + "wires": [ + [ + "2b639346c1b56578" + ] + ] + }, + { + "id": "2b639346c1b56578", + "type": "function", + "z": "e43a27722b508115", + "name": "function 2", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'shield_type';\nconst current_shield = fs.readFileSync(filepath + file, 'utf8');\n\nvar current_choice = msg.payload\n\nif (current_choice != current_shield) {\n \n switch (current_choice) {\n case \"green\":\n fs.writeFile(filepath + 'pin_external', String(10), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(17), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(9), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(11), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(14), err => {\n if (err) {\n return\n }\n });\n break;\n case \"black\":\n fs.writeFile(filepath + 'pin_external', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(24), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(22), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(17), err => {\n if (err) {\n return\n }\n });\n break;\n case \"custom\":\n break;\n }\n\n fs.writeFile(filepath + file, current_choice, err => {\n if (err) {\n return\n }\n });\n}\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 460, + "y": 3840, + "wires": [ + [] + ] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", @@ -9504,8 +9539,8 @@ "50eeb3e362f9027f", "960912e90ba5b5bc" ], - "x": 115, - "y": 420, + "x": 55, + "y": 120, "wires": [ [ "f128ca405d1e1e4d", @@ -9526,8 +9561,8 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 425, - "y": 407, + "x": 450, + "y": 180, "wires": [] }, { @@ -9542,8 +9577,8 @@ "winHide": false, "oldrc": false, "name": "Successful Scans", - "x": 250, - "y": 420, + "x": 210, + "y": 180, "wires": [ [ "cd0dc08fcb5968c8" @@ -9566,7 +9601,7 @@ "layout": "row-spread", "className": "", "x": 440, - "y": 340, + "y": 120, "wires": [] }, { @@ -9581,8 +9616,8 @@ "winHide": false, "oldrc": false, "name": "Aborted Scans", - "x": 245, - "y": 353, + "x": 200, + "y": 120, "wires": [ [ "b91b4d65f2090793" @@ -9590,5 +9625,15 @@ [], [] ] + }, + { + "id": "5b3aa9a71591ba34", + "type": "comment", + "z": "87715429b0b1c9a3", + "name": "Statistics", + "info": "", + "x": 100, + "y": 40, + "wires": [] } ] \ No newline at end of file diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index 667c23e..3c0f601 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -88,7 +88,7 @@ "4": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 333492 + "filesize": 337770 }, "5": { "src": "meanwhile/settings.js", From ae1bbbf4ed2445704bc4ada221f4b65b672da419 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 29 Sep 2024 12:51:43 +0200 Subject: [PATCH 15/38] move the rotor before tt --- update/2024-11S/meanwhile/flows.json | 219 ++++++++++++++++++++++----- update/2024-11S/update.json | 2 +- 2 files changed, 180 insertions(+), 41 deletions(-) diff --git a/update/2024-11S/meanwhile/flows.json b/update/2024-11S/meanwhile/flows.json index bf7892f..e2e8c61 100644 --- a/update/2024-11S/meanwhile/flows.json +++ b/update/2024-11S/meanwhile/flows.json @@ -523,6 +523,37 @@ "collapse": false, "className": "" }, + { + "id": "e357ef02.ef3cb", + "type": "ui_group", + "name": "Page 1", + "tab": "4bc17c6e.b74934", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "c9165343995892c6", + "type": "ui_group", + "name": "Page 2", + "tab": "", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "4bc17c6e.b74934", + "type": "ui_tab", + "name": "Page 1", + "icon": "wi-wu-tstorms", + "order": 5, + "disabled": false, + "hidden": false + }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -545,7 +576,7 @@ "payload": "", "payloadType": "date", "x": 100, - "y": 460, + "y": 520, "wires": [ [ "949bafced17d66d6" @@ -564,7 +595,7 @@ "finalize": "", "libs": [], "x": 250, - "y": 460, + "y": 520, "wires": [ [] ] @@ -600,7 +631,8 @@ "b1e2491c952f84c9", "fac6626127bba4f5", "bc2f0adaf72f97e9", - "ac242724fe7605a6" + "ac242724fe7605a6", + "d81572486f15cd7a" ] ] }, @@ -718,7 +750,7 @@ "payload": "", "payloadType": "str", "x": 100, - "y": 380, + "y": 440, "wires": [ [ "6c6ef2255a7d39e5" @@ -737,7 +769,7 @@ "6bf8344af427a6ba" ], "x": 205, - "y": 380, + "y": 440, "wires": [] }, { @@ -871,14 +903,14 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "global", "className": "", - "x": 580, - "y": 140, + "x": 650, + "y": 360, "wires": [ [] ] @@ -917,7 +949,7 @@ "topic": "", "topicType": "str", "x": 90, - "y": 540, + "y": 600, "wires": [ [ "62cd5288.2805fc" @@ -945,7 +977,7 @@ "topic": "", "topicType": "str", "x": 100, - "y": 620, + "y": 680, "wires": [ [ "62cd5288.2805fc" @@ -959,7 +991,7 @@ "name": "", "events": "all", "x": 280, - "y": 540, + "y": 600, "wires": [ [] ] @@ -985,7 +1017,7 @@ "topic": "", "topicType": "str", "x": 90, - "y": 580, + "y": 640, "wires": [ [ "62cd5288.2805fc" @@ -1013,7 +1045,7 @@ "topic": "", "topicType": "str", "x": 110, - "y": 660, + "y": 720, "wires": [ [ "62cd5288.2805fc" @@ -1131,36 +1163,23 @@ "name": "started1s", "mode": "link", "links": [ + "1dffb799fdf10cbc", + "2afb6a45c73fa244", + "2e9b29c70969cf01", "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "b4c843620c251c43", "3876d5cbd248592b", - "a4c81754c148b86f", - "2e9b29c70969cf01", - "2477f81cddc8fa31", - "29036b35dfd672c6", "592ec13d8f8923a9", + "5e7d5e4335d37794", + "9b3e6a06c82a0f52", + "9fd259de91de1da1", + "b4c843620c251c43", "cb40b9341bd22a28", + "d0104e0163745993", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244", - "9b3e6a06c82a0f52", - "fbc5fc2e65311f8b" + "e5f38b4a07a5e278", + "fbc5fc2e65311f8b", + "fd0258418489839d" ], "x": 1015, "y": 540, @@ -1250,13 +1269,77 @@ "topic": "topic", "topicType": "msg", "x": 100, - "y": 700, + "y": 760, "wires": [ [ "62cd5288.2805fc" ] ] }, + { + "id": "d81572486f15cd7a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "loadl", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = String(data);\nflow.set('openscan_version',data)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 360, + "wires": [ + [ + "b1b0ccb783dd5882" + ] + ] + }, + { + "id": "fa6db57803ae2b6d", + "type": "debug", + "z": "e6f4d02efb300ea9", + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": true, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 590, + "y": 460, + "wires": [] + }, + { + "id": "b1b0ccb783dd5882", + "type": "change", + "z": "e6f4d02efb300ea9", + "name": "openscan_version", + "rules": [ + { + "t": "set", + "p": "openscan_version", + "pt": "flow", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 415, + "y": 360, + "wires": [ + [ + "fa6db57803ae2b6d", + "0d8c6bc7887fb3c2" + ] + ] + }, { "id": "6a3d9acbe097a3d2", "type": "function", @@ -2209,7 +2292,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -8686,7 +8769,7 @@ "type": "function", "z": "e43a27722b508115", "name": "function 2", - "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'shield_type';\nconst current_shield = fs.readFileSync(filepath + file, 'utf8');\n\nvar current_choice = msg.payload\n\nif (current_choice != current_shield) {\n \n switch (current_choice) {\n case \"green\":\n fs.writeFile(filepath + 'pin_external', String(10), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(17), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(9), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(11), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(14), err => {\n if (err) {\n return\n }\n });\n break;\n case \"black\":\n fs.writeFile(filepath + 'pin_external', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(24), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(22), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(17), err => {\n if (err) {\n return\n }\n });\n break;\n case \"custom\":\n break;\n }\n\n fs.writeFile(filepath + file, current_choice, err => {\n if (err) {\n return\n }\n });\n}\n\nreturn msg;", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'shield_type';\nconst current_shield = fs.readFileSync(filepath + file, 'utf8');\n\nvar current_choice = msg.payload\n\nif (current_choice != current_shield) {\n \n switch (current_choice) {\n case \"green\":\n fs.writeFile(filepath + 'pin_external', String(10), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(17), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(9), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(11), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(14), err => {\n if (err) {\n return\n }\n });\n break;\n case \"black\":\n fs.writeFile(filepath + 'pin_external', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(24), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(22), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(17), err => {\n if (err) {\n return\n }\n });\n break;\n case \"custom\":\n break;\n }\n\n fs.writeFile(filepath + file, current_choice, err => {\n if (err) {\n return\n }\n });\n}\n\nmsg.status = 'The new ' + current_choice + ' shield has been configured'\nmsg.topic = msg.status\nmsg.payload = 'Do you want to reboot now to apply the changes?'\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -8694,10 +8777,66 @@ "libs": [], "x": 460, "y": 3840, + "wires": [ + [ + "137f032887544d74" + ] + ] + }, + { + "id": "137f032887544d74", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": false, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Reboot", + "x": 600, + "y": 3840, + "wires": [ + [ + "d2db49796fe0da79", + "d0d6820224b0ab0f" + ] + ] + }, + { + "id": "d2db49796fe0da79", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "print(msg['payload'])\nreturn msg", + "outputs": 1, + "x": 790, + "y": 3840, "wires": [ [] ] }, + { + "id": "d0d6820224b0ab0f", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 3720, + "wires": [] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index 3c0f601..01083af 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -88,7 +88,7 @@ "4": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 337770 + "filesize": 341336 }, "5": { "src": "meanwhile/settings.js", From 972cddfbe1ac7e3b82efb80b9f682422e2c9aabe Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 29 Sep 2024 13:09:45 +0200 Subject: [PATCH 16/38] change the movement order, first rotor, then tt --- update/2024-11S/stable/flows.json | 4 ++-- update/2024-11S/update.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/update/2024-11S/stable/flows.json b/update/2024-11S/stable/flows.json index 3556333..1c11a12 100644 --- a/update/2024-11S/stable/flows.json +++ b/update/2024-11S/stable/flows.json @@ -830,7 +830,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n", + "format": "\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -2142,7 +2142,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\nimport os\n\ntry:\n os.remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n \n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\nimport os\n\ntry:\n os.remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, diff --git a/update/2024-11S/update.json b/update/2024-11S/update.json index 01083af..4fd7155 100644 --- a/update/2024-11S/update.json +++ b/update/2024-11S/update.json @@ -18,7 +18,7 @@ "4": { "src": "stable/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 327504 + "filesize": 327566 }, "5": { "src": "stable/settings.js", From 3febe33d0a8e9b30864b0eb4d7be76793d9ce705 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 29 Sep 2024 19:05:58 +0200 Subject: [PATCH 17/38] first 2024-12o version --- update/2024-12o/beta/OpenScan.py | 312 + update/2024-12o/beta/OpenScanStatistics.py | 18 + update/2024-12o/beta/config.txt | 34 + update/2024-12o/beta/fla.py | 519 + update/2024-12o/beta/flows.json | 9594 ++++++++++++++++ update/2024-12o/beta/settings.js | 512 + update/2024-12o/meanwhile/OpenScan.py | 312 + .../2024-12o/meanwhile/OpenScanStatistics.py | 18 + update/2024-12o/meanwhile/config.txt | 34 + update/2024-12o/meanwhile/fla.py | 519 + update/2024-12o/meanwhile/flows.json | 9778 +++++++++++++++++ update/2024-12o/meanwhile/settings.js | 512 + update/2024-12o/scripts/expand_root.sh | 7 + update/2024-12o/scripts/startup.sh | 11 + update/2024-12o/stable/OpenScan.py | 312 + update/2024-12o/stable/OpenScanStatistics.py | 18 + update/2024-12o/stable/config.txt | 84 + update/2024-12o/stable/fla.py | 519 + update/2024-12o/stable/flows.json | 9378 ++++++++++++++++ update/2024-12o/stable/settings.js | 512 + update/2024-12o/update.json | 114 + 21 files changed, 33117 insertions(+) create mode 100644 update/2024-12o/beta/OpenScan.py create mode 100755 update/2024-12o/beta/OpenScanStatistics.py create mode 100755 update/2024-12o/beta/config.txt create mode 100644 update/2024-12o/beta/fla.py create mode 100644 update/2024-12o/beta/flows.json create mode 100644 update/2024-12o/beta/settings.js create mode 100644 update/2024-12o/meanwhile/OpenScan.py create mode 100755 update/2024-12o/meanwhile/OpenScanStatistics.py create mode 100755 update/2024-12o/meanwhile/config.txt create mode 100644 update/2024-12o/meanwhile/fla.py create mode 100644 update/2024-12o/meanwhile/flows.json create mode 100644 update/2024-12o/meanwhile/settings.js create mode 100755 update/2024-12o/scripts/expand_root.sh create mode 100755 update/2024-12o/scripts/startup.sh create mode 100644 update/2024-12o/stable/OpenScan.py create mode 100755 update/2024-12o/stable/OpenScanStatistics.py create mode 100755 update/2024-12o/stable/config.txt create mode 100644 update/2024-12o/stable/fla.py create mode 100644 update/2024-12o/stable/flows.json create mode 100644 update/2024-12o/stable/settings.js create mode 100644 update/2024-12o/update.json diff --git a/update/2024-12o/beta/OpenScan.py b/update/2024-12o/beta/OpenScan.py new file mode 100644 index 0000000..e634511 --- /dev/null +++ b/update/2024-12o/beta/OpenScan.py @@ -0,0 +1,312 @@ +basepath = '/home/pi/OpenScan/' +from os.path import isfile +import os + +def load_bool(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + if value == '1' or value == 'True' or value =='true': + value = True + else: + value = False + return value + +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + return True + +def load_str(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + return value + +def load_int(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = int(file.read().replace('\n','')) + return value + +def load_float(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = float(file.read().replace('\n','')) + return value + +def save(name, value): + filename = basepath+'settings/'+name + with open(filename, 'w+') as file: + file.write(str(value)) + return + +def OpenScanCloud(cmd, msg): + from requests import get + osc_user = 'openscan' + osc_pw = 'free' + osc_server = 'http://openscanfeedback.dnsuser.de:1334/' + + try: + r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) + except: + r = type('obj', (object,), {'status_code' : 404, 'text':None}) + return r + +def camera(cmd, msg = {}): + from requests import get + flask = 'http://127.0.0.1:1312/' + try: + r = get(flask + cmd, params=msg) + return r.status_code + except: + return 400 + +def motorrun(motor,angle,ES_enable=False,ES_start_state = True): + #motor can be "rotor", "tt" or "extra" + import RPi.GPIO as GPIO + from time import sleep + from math import cos + msg = {'cmd':'set'} + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + + spr = load_int(motor + '_stepsperrotation') + dirpin = load_int('pin_' + motor + '_dir') + steppin = load_int('pin_' + motor +'_step') + ES_pin = load_int('pin_' + motor + '_endstop') + dir = load_int(motor + '_dir') + ramp = load_int(motor + '_accramp') + acc = load_float(motor + '_acc') + delay_init = load_float(motor + '_delay') + delay = delay_init + + step_count=int(angle*spr/360) * dir + GPIO.setup(dirpin, GPIO.OUT) + GPIO.setup(steppin, GPIO.OUT) + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + + if (step_count>0): + GPIO.output(dirpin, GPIO.HIGH) + if(step_count<0): + GPIO.output(dirpin, GPIO.LOW) + step_count=-step_count + for x in range(step_count): + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + + GPIO.output(steppin, GPIO.HIGH) + if x<=ramp and x<=step_count/2: + delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) + #delay=delay_init+(ramp-x)*(delay_init)/acc + elif step_count-x<=ramp and x>step_count/2: + delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) + #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc + else: + delay = delay_init + sleep(delay) + GPIO.output(steppin, GPIO.LOW) + sleep(delay) + +def ringlight(number,state): + import RPi.GPIO as GPIO + msg = {'cmd':'set'} + pin = load_int('pin_ringlight' + str(number)) + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(pin, GPIO.OUT) + GPIO.output(pin, state) + +def take_photo(file): + from os import system + filepath = basepath + file + + model=load_str('model') + + shutter = str(load_int('cam_shutter')) + saturation = load_str('cam_saturation') + contrast = load_str('cam_contrast') + awbg_red = load_str('cam_awbg_red') + awbg_blue = load_str('cam_awbg_blue') + gain = load_str('cam_gain') + quality = load_int('cam_jpeg_quality') + filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' + #width = load_str('cam_resx') + #height = load_str('cam_resy') + timeout = load_str('cam_timeout') + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + AF = load_bool('cam_AFmode') + camera = load_str('camera') + + + if camera == 'imx519' and AF == True: + autofocus = ' --autofocus ' + else: + autofocus = '' + + if camera == "usb_webcam": + cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 + else: + cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' + # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + + system(cmd) + return cmd + +def get_points(samples=1): + from math import pi, sqrt, acos, atan2, cos, sin + + points = [] + phi = pi * (3. - sqrt(5.)) + for i in range(int(samples)): + y = 1 - (i / float(samples - 1)) * 2 + radius = sqrt(1 - y * y) + theta = phi * i + x = cos(theta) * radius + z = sin(theta) * radius + r=sqrt(x*x+y*y+z*z) + theta_neu=acos(z/r)*180/pi + phi_neu=atan2(y,x)*180/pi + points.append((theta_neu-90,phi_neu)) + points.sort() + return points + +def create_coordinates(angle_min, angle_max,point_count): + point_count_final=point_count + if angle_max < angle_min: + a = angle_min + angle_min = angle_max + angle_max = a + point_count=point_count*90/(angle_max-angle_min) + actual_points=0 + while actual_pointsangle_min and x20: + point_count=point_count+3 + else: + point_count=point_count+1 + return filtered + + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/2024-12o/beta/OpenScanStatistics.py b/update/2024-12o/beta/OpenScanStatistics.py new file mode 100755 index 0000000..68005af --- /dev/null +++ b/update/2024-12o/beta/OpenScanStatistics.py @@ -0,0 +1,18 @@ +import csv + +class ScanStatistics: + def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): + self.filename = filename + self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + + def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): + data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] + + with open(self.filename, "a", newline='') as csv_file: + csv_writer = csv.writer(csv_file, delimiter=';') + + # Write header if file is empty + if csv_file.tell() == 0: + csv_writer.writerow(self.header) + + csv_writer.writerow(data) diff --git a/update/2024-12o/beta/config.txt b/update/2024-12o/beta/config.txt new file mode 100755 index 0000000..3bb8fef --- /dev/null +++ b/update/2024-12o/beta/config.txt @@ -0,0 +1,34 @@ +# For more options and information see +# http://rpf.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Additional overlays and parameters are documented /boot/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[pi4] +# Run as fast as firmware / board allows +arm_boost=1 +dtoverlay=imx519,cma-512 + +[all] +camera_auto_detect=0 +gpu_mem=256 +dtoverlay=imx519 \ No newline at end of file diff --git a/update/2024-12o/beta/fla.py b/update/2024-12o/beta/fla.py new file mode 100644 index 0000000..026867e --- /dev/null +++ b/update/2024-12o/beta/fla.py @@ -0,0 +1,519 @@ +from flask import Flask, request, redirect, send_file, send_from_directory +from flask_restx import Resource, Api, Namespace +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont +from time import sleep, time +from OpenScan import load_int, load_float, load_bool, ringlight, motorrun +import RPi.GPIO as GPIO +from math import sqrt +import os +import math +from skimage import feature, color, transform +import numpy as np +from scipy import ndimage +import socket + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +app = Flask(__name__) +api = Api(app, version='1.0', title='OpenScan API', description='API for OpenScan') + +v1 = Namespace('v1', description='API v1') +# Create a namespace for system operations +system_ns = Namespace('system', description='System operations') +camera_ns = Namespace('camera', description='Camera operations') +motor_ns = Namespace('motor', description='Motor operations') + +api.add_namespace(v1, path='/v1') +api.add_namespace(system_ns, path='/v1/system') +api.add_namespace(camera_ns, path='/v1/camera') +api.add_namespace(motor_ns, path='/v1/motor') + +basedir = '/home/pi/OpenScan/' +timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + + +################################################################################################################### + + +@system_ns.route('/status') +class Status(Resource): + def get(self): + ''' + Get system status + ''' + import os + import json + from time import time + + if os.path.exists('/tmp/status.json'): + try: + with open('/tmp/status.json', 'r') as status_file: + status = json.load(status_file) + + elapsed_time = time() - status['start_time'] + estimated_total_time = (elapsed_time / status['current_photo']) * status['total_photos'] + time_remaining = max(0, estimated_total_time - elapsed_time) + + status.update({ + "status": "running", + "elapsed_time": int(elapsed_time), + "estimated_total_time": int(estimated_total_time), + "time_remaining": int(time_remaining) + }) + + return status, 200 + except Exception as e: + return {"error": f"Error reading status file: {str(e)}"}, 500 + else: + return {"status": "idle"}, 200 + +@system_ns.route('/shutdown') +class Shutdown(Resource): + @system_ns.doc(params={'token': 'Shutdown token for authentication'}) + def get(self): + '''Shutdown the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('shutdown -h now') + return {'message': 'Shutting down'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/reboot') +class Reboot(Resource): + @system_ns.doc(params={'token': 'Reboot token for authentication'}) + def get(self): + '''Reboot the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('reboot -h') + return {'message': 'Rebooting'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/ringlight') +class Ringlight(Resource): + @system_ns.doc(params={'state': 'Ringlight state (0 or 1)'}) + def get(self): + '''Set ringlight state''' + state = int(request.args.get('state')) + if state == 0: + ringlight(1, False) + ringlight(2, False) + else: + ringlight(1, True) + ringlight(2, True) + return {'message': f'Ringlight set to {state}'}, 200 + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) + + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') + + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wait 3-5s + return {'message': 'Auto focus triggered'}, 200 + +@motor_ns.route('/motor_run') +class MotorRun(Resource): + ''' + Run a motor + ''' + @motor_ns.doc(params={ + 'motor': 'Motor name (rotor, tt, extra)', + 'angle': 'Angle to rotate (integer)', + 'ES_enable': 'Enable endstop (optional, boolean)', + 'ES_start_state': 'Endstop start state (optional, boolean)' + }) + @motor_ns.response(400, 'Bad Request') + def get(self): + '''Run a motor''' + motor = request.args.get('motor') + if not motor: + return {'error': 'Motor parameter is required'}, 400 + if motor not in ['rotor', 'tt', 'extra']: + return {'error': 'Invalid motor name'}, 400 + + try: + angle = int(request.args.get('angle')) + except (TypeError, ValueError): + return {'error': 'Angle must be an integer'}, 400 + + ES_enable = request.args.get('ES_enable', 'false').lower() == 'true' + ES_start_state = request.args.get('ES_start_state', 'true').lower() == 'true' + + try: + motorrun(motor, angle, ES_enable, ES_start_state) + except Exception as e: + return {'error': f'Error running motor: {str(e)}'}, 500 + + return {'message': f'Motor {motor} run to {angle} degrees'}, 200 + + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + + +if __name__ == '__main__': +# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) + app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) + diff --git a/update/2024-12o/beta/flows.json b/update/2024-12o/beta/flows.json new file mode 100644 index 0000000..e0db7f2 --- /dev/null +++ b/update/2024-12o/beta/flows.json @@ -0,0 +1,9594 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "87715429b0b1c9a3", + "type": "tab", + "label": "Statistics", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "#097479", + "value": "#097479", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#097479", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "#097479", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "ac59b8fb186de073", + "type": "ui_group", + "name": "[Statistics]", + "tab": "656b4eb8b15dab8f", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "656b4eb8b15dab8f", + "type": "ui_tab", + "name": "Statistics", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 460, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244", + "9b3e6a06c82a0f52" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 380, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 380, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 820, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 820, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244", + "9b3e6a06c82a0f52" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "e548168473aa85d6", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 5, + "width": 0, + "height": 0, + "passthru": false, + "label": "Statistics", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "5", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 100, + "y": 700, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/v1/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/v1/camera/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 535, + "y": 1060, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = \"green\"\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n \ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/v1/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2100, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2340, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2140, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2180, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2060, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2140, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2180, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2100, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2060, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2380, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2420, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2300, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2420, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2300, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2380, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2340, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2220, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2220, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2460, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2500, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2260, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2500, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2260, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2600, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2640, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2760, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2800, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2600, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2640, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2760, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2800, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2840, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3000, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3000, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3040, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3040, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3120, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3120, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3160, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3160, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3240, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3240, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3280, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3280, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3320, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3320, + "wires": [] + }, + { + "id": "74e455136b5ca5dd", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 21, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3360, + "wires": [ + [ + "a4a89668ce4c9f05" + ] + ] + }, + { + "id": "3a74f653800eb831", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, + "width": 4, + "height": 1, + "name": "endstop2", + "label": "Endstop Turntable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3360, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2880, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2600, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2600, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2760, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2800, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3000, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3000, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3040, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3040, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "21dc963d967d9c99", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3360, + "wires": [ + [ + "74e455136b5ca5dd" + ] + ] + }, + { + "id": "a4a89668ce4c9f05", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3360, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 7, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88", + "53681e53353db898" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2560, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2560, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 560, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 560, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, + { + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "e98c1b83744bb863", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Delete Aborted", + "tooltip": "Delete aborted photosets", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 420, + "y": 520, + "wires": [ + [ + "7438a5bf5fcddec4" + ] + ] + }, + { + "id": "7438a5bf5fcddec4", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "delete_aborted", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('delete_aborted'):\n save('delete_aborted', state)\n", + "outputs": 1, + "x": 600, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "53681e53353db898", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'delete_aborted'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 520, + "wires": [ + [ + "e98c1b83744bb863" + ] + ] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 9, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "9b3e6a06c82a0f52", + "type": "link in", + "z": "87715429b0b1c9a3", + "name": "", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 115, + "y": 420, + "wires": [ + [ + "f128ca405d1e1e4d", + "07d7ce3dab5f1c11" + ] + ] + }, + { + "id": "cd0dc08fcb5968c8", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Successful Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 425, + "y": 407, + "wires": [] + }, + { + "id": "f128ca405d1e1e4d", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Successful Scans", + "x": 250, + "y": 420, + "wires": [ + [ + "cd0dc08fcb5968c8" + ], + [], + [] + ] + }, + { + "id": "b91b4d65f2090793", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Aborted Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 440, + "y": 340, + "wires": [] + }, + { + "id": "07d7ce3dab5f1c11", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Aborted Scans", + "x": 245, + "y": 353, + "wires": [ + [ + "b91b4d65f2090793" + ], + [], + [] + ] + } +] \ No newline at end of file diff --git a/update/2024-12o/beta/settings.js b/update/2024-12o/beta/settings.js new file mode 100644 index 0000000..357b02b --- /dev/null +++ b/update/2024-12o/beta/settings.js @@ -0,0 +1,512 @@ +/** + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT + * + * It can contain any valid JavaScript code that will get run when Node-RED + * is started. + * + * Lines that start with // are commented out. + * Each entry should be separated from the entries above and below by a comma ',' + * + * For more information about individual settings, refer to the documentation: + * https://nodered.org/docs/user-guide/runtime/configuration + * + * The settings are split into the following sections: + * - Flow File and User Directory Settings + * - Security + * - Server Settings + * - Runtime Settings + * - Editor Settings + * - Node Settings + * + **/ +process.env.HOSTNAME = require('os').hostname(); + +module.exports = { + +/******************************************************************************* + * Flow File and User Directory Settings + * - flowFile + * - credentialSecret + * - flowFilePretty + * - userDir + * - nodesDir + ******************************************************************************/ + + /** The file containing the flows. If not set, defaults to flows_.json **/ + flowFile: "flows.json", + + /** By default, credentials are encrypted in storage using a generated key. To + * specify your own secret, set the following property. + * If you want to disable encryption of credentials, set this property to false. + * Note: once you set this property, do not change it - doing so will prevent + * node-red from being able to decrypt your existing credentials and they will be + * lost. + */ + credentialSecret: false, + + /** By default, the flow JSON will be formatted over multiple lines making + * it easier to compare changes when using version control. + * To disable pretty-printing of the JSON set the following property to false. + */ + flowFilePretty: true, + + /** By default, all user data is stored in a directory called `.node-red` under + * the user's home directory. To use a different location, the following + * property can be used + */ + //userDir: '/home/nol/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + + /** Node-RED scans the `nodes` directory in the userDir to find local node files. + * The following property can be used to specify an additional directory to scan. + */ + //nodesDir: '/home/nol/.node-red/nodes', + +/******************************************************************************* + * Security + * - adminAuth + * - https + * - httpsRefreshInterval + * - requireHttps + * - httpNodeAuth + * - httpStaticAuth + ******************************************************************************/ + + /** To password protect the Node-RED editor and admin API, the following + * property can be used. See http://nodered.org/docs/security.html for details. + */ + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + /** The following property can be used to enable HTTPS + * This property can be either an object, containing both a (private) key + * and a (public) certificate, or a function that returns such an object. + * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + * for details of its contents. + */ + + /** Option 1: static object */ + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + + /** Option 2: function that returns the HTTP configuration object */ + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + /** If the `https` setting is a function, the following setting can be used + * to set how often, in hours, the function will be called. That can be used + * to refresh any certificates. + */ + //httpsRefreshInterval : 12, + + /** The following property can be used to cause insecure HTTP connections to + * be redirected to HTTPS. + */ + //requireHttps: true, + + /** To password protect the node-defined HTTP endpoints (httpNodeRoot), + * including node-red-dashboard, or the static content (httpStatic), the + * following properties can be used. + * The `pass` field is a bcrypt hash of the password. + * See http://nodered.org/docs/security.html#generating-the-password-hash + */ + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + +/******************************************************************************* + * Server Settings + * - uiPort + * - uiHost + * - apiMaxLength + * - httpServerOptions + * - httpAdminRoot + * - httpAdminMiddleware + * - httpNodeRoot + * - httpNodeCors + * - httpNodeMiddleware + * - httpStatic + * - httpStaticRoot + ******************************************************************************/ + + /** the tcp port that the Node-RED web server is listening on */ + uiPort: process.env.PORT || 80, + + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. + * To listen on all IPv6 addresses, set uiHost to "::", + * The following property can be used to listen on a specific interface. For + * example, the following would only allow connections from the local machine. + */ + //uiHost: "127.0.0.1", + + /** The maximum size of HTTP request that will be accepted by the runtime api. + * Default: 5mb + */ + //apiMaxLength: '5mb', + + /** The following property can be used to pass custom options to the Express.js + * server used by Node-RED. For a full list of available options, refer + * to http://expressjs.com/en/api.html#app.settings.table + */ + //httpServerOptions: { }, + + /** By default, the Node-RED UI is available at http://localhost:1880/ + * The following property can be used to specify a different root path. + * If set to false, this is disabled. + */ + httpAdminRoot: '/editor', + + /** The following property can be used to add a custom middleware function + * in front of all admin http routes. For example, to set custom http + * headers. It can be a single function or an array of middleware functions. + */ + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + + /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. + * By default, these are served relative to '/'. The following property + * can be used to specifiy a different root path. If set to false, this is + * disabled. + */ + //httpNodeRoot: '/red-nodes', + + /** The following property can be used to configure cross-origin resource sharing + * in the HTTP nodes. + * See https://github.com/troygoode/node-cors#configuration-options for + * details on its contents. The following is a basic permissive set of options: + */ + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + /** If you need to set an http proxy please set an environment variable + * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + * For example - http_proxy=http://myproxy.com:8080 + * (Setting it here will have no effect) + * You may also specify no_proxy (or NO_PROXY) to supply a comma separated + * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + */ + + /** The following property can be used to add a custom middleware function + * in front of all http in nodes. This allows custom authentication to be + * applied to all http in nodes, or any other sort of common request processing. + * It can be a single function or an array of middleware functions. + */ + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + /** When httpAdminRoot is used to move the UI to a different root path, the + * following property can be used to identify a directory of static content + * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot + */ + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + +/******************************************************************************* + * Runtime Settings + * - lang + * - logging + * - contextStorage + * - exportGlobalContextKeys + * - externalModules + ******************************************************************************/ + + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", + + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, + + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, + + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: [], + // denyList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, + + +/******************************************************************************* + * Editor Settings + * - disableEditor + * - editorTheme + ******************************************************************************/ + + /** The following property can be used to disable the editor. The admin API + * is not affected by this option. To disable both the editor and the admin + * API, use either the httpRoot or httpAdminRoot properties + */ + //disableEditor: false, + + /** Customising the editor + * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes + * for all available options. + */ + editorTheme: { + /** The following property can be used to set a custom theme for the editor. + * See https://github.com/node-red-contrib-themes/theme-collection for + * a collection of themes to chose from. + */ + //theme: "", + palette: { + /** The following property can be used to order the categories in the editor + * palette. If a node's category is not in the list, the category will get + * added to the end of the palette. + * If not set, the following default order is used: + */ + //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + }, + projects: { + /** To enable the Projects feature, set this value to true */ + enabled: false, + workflow: { + /** Set the default projects workflow mode. + * - manual - you must manually commit changes + * - auto - changes are automatically committed + * This can be overridden per-user from the 'Git config' + * section of 'User Settings' within the editor + */ + mode: "manual" + } + }, + codeEditor: { + /** Select the text editor component used by the editor. + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired + */ + lib: "monaco", + options: { + /** The follow options only apply if the editor is set to "monaco" + * + * theme - must match the file name of a theme in + * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme + * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" + */ + theme: "vs", + /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html + */ + //fontSize: 14, + //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", + //fontLigatures: true, + } + } + }, + +/******************************************************************************* + * Node Settings + * - fileWorkingDirectory + * - functionGlobalContext + * - functionExternalModules + * - nodeMessageBufferMaxLength + * - ui (for use with Node-RED Dashboard) + * - debugUseColors + * - debugMaxLength + * - execMaxBufferSize + * - httpRequestTimeout + * - mqttReconnectTime + * - serialReconnectTime + * - socketReconnectTime + * - socketTimeout + * - tcpMsgQueueSize + * - inboundWebSocketTimeout + * - tlsConfigDisableLocalFiles + * - webSocketNodeVerifyClient + ******************************************************************************/ + + /** The working directory to handle relative file paths from within the File nodes + * defaults to the working directory of the Node-RED process. + */ + //fileWorkingDirectory: "", + + /** Allow the Function node to load additional npm modules directly */ + functionExternalModules: true, + + /** The following property can be used to set predefined values in Global Context. + * This allows extra node modules to be made available with in Function node. + * For example, the following: + * functionGlobalContext: { os:require('os') } + * will allow the `os` module to be accessed in a Function node using: + * global.get("os") + */ +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, + /** The maximum number of messages nodes will buffer internally as part of their + * operation. This applies across a range of nodes that operate on message sequences. + * defaults to no limit. A value of 0 also means no limit is applied. + */ + //nodeMessageBufferMaxLength: 0, + + /** If you installed the optional node-red-dashboard you can set it's path + * relative to httpNodeRoot + * Other optional properties include + * readOnly:{boolean}, + * middleware:{function or array}, (req,res,next) - http middleware + * ioMiddleware:{function or array}, (socket,next) - socket.io middleware + */ + ui: { path: "" }, + + /** Colourise the console output of the debug node */ + //debugUseColors: true, + + /** The maximum length, in characters, of any message sent to the debug sidebar tab */ + debugMaxLength: 1000, + + /** Maximum buffer size for the exec node. Defaults to 10Mb */ + //execMaxBufferSize: 10000000, + + /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ + //httpRequestTimeout: 120000, + + /** Retry time in milliseconds for MQTT connections */ + mqttReconnectTime: 15000, + + /** Retry time in milliseconds for Serial port connections */ + serialReconnectTime: 15000, + + /** Retry time in milliseconds for TCP socket connections */ + //socketReconnectTime: 10000, + + /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ + //socketTimeout: 120000, + + /** Maximum number of messages to wait in queue while attempting to connect to TCP socket + * defaults to 1000 + */ + //tcpMsgQueueSize: 2000, + + /** Timeout in milliseconds for inbound WebSocket connections that do not + * match any configured node. Defaults to 5000 + */ + //inboundWebSocketTimeout: 5000, + + /** To disable the option for using local files for storing keys and + * certificates in the TLS configuration node, set this to true. + */ + //tlsConfigDisableLocalFiles: true, + + /** The following property can be used to verify websocket connection attempts. + * This allows, for example, the HTTP request headers to be checked to ensure + * they include valid authentication information. + */ + //webSocketNodeVerifyClient: function(info) { + // /** 'info' has three properties: + // * - origin : the value in the Origin header + // * - req : the HTTP request + // * - secure : true if req.connection.authorized or req.connection.encrypted is set + // * + // * The function should return true if the connection should be accepted, false otherwise. + // * + // * Alternatively, if this function is defined to accept a second argument, callback, + // * it can be used to verify the client asynchronously. + // * The callback takes three arguments: + // * - result : boolean, whether to accept the connection or not + // * - code : if result is false, the HTTP error status to return + // * - reason: if result is false, the HTTP reason string to return + // */ + //}, +} diff --git a/update/2024-12o/meanwhile/OpenScan.py b/update/2024-12o/meanwhile/OpenScan.py new file mode 100644 index 0000000..e634511 --- /dev/null +++ b/update/2024-12o/meanwhile/OpenScan.py @@ -0,0 +1,312 @@ +basepath = '/home/pi/OpenScan/' +from os.path import isfile +import os + +def load_bool(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + if value == '1' or value == 'True' or value =='true': + value = True + else: + value = False + return value + +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + return True + +def load_str(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + return value + +def load_int(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = int(file.read().replace('\n','')) + return value + +def load_float(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = float(file.read().replace('\n','')) + return value + +def save(name, value): + filename = basepath+'settings/'+name + with open(filename, 'w+') as file: + file.write(str(value)) + return + +def OpenScanCloud(cmd, msg): + from requests import get + osc_user = 'openscan' + osc_pw = 'free' + osc_server = 'http://openscanfeedback.dnsuser.de:1334/' + + try: + r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) + except: + r = type('obj', (object,), {'status_code' : 404, 'text':None}) + return r + +def camera(cmd, msg = {}): + from requests import get + flask = 'http://127.0.0.1:1312/' + try: + r = get(flask + cmd, params=msg) + return r.status_code + except: + return 400 + +def motorrun(motor,angle,ES_enable=False,ES_start_state = True): + #motor can be "rotor", "tt" or "extra" + import RPi.GPIO as GPIO + from time import sleep + from math import cos + msg = {'cmd':'set'} + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + + spr = load_int(motor + '_stepsperrotation') + dirpin = load_int('pin_' + motor + '_dir') + steppin = load_int('pin_' + motor +'_step') + ES_pin = load_int('pin_' + motor + '_endstop') + dir = load_int(motor + '_dir') + ramp = load_int(motor + '_accramp') + acc = load_float(motor + '_acc') + delay_init = load_float(motor + '_delay') + delay = delay_init + + step_count=int(angle*spr/360) * dir + GPIO.setup(dirpin, GPIO.OUT) + GPIO.setup(steppin, GPIO.OUT) + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + + if (step_count>0): + GPIO.output(dirpin, GPIO.HIGH) + if(step_count<0): + GPIO.output(dirpin, GPIO.LOW) + step_count=-step_count + for x in range(step_count): + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + + GPIO.output(steppin, GPIO.HIGH) + if x<=ramp and x<=step_count/2: + delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) + #delay=delay_init+(ramp-x)*(delay_init)/acc + elif step_count-x<=ramp and x>step_count/2: + delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) + #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc + else: + delay = delay_init + sleep(delay) + GPIO.output(steppin, GPIO.LOW) + sleep(delay) + +def ringlight(number,state): + import RPi.GPIO as GPIO + msg = {'cmd':'set'} + pin = load_int('pin_ringlight' + str(number)) + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(pin, GPIO.OUT) + GPIO.output(pin, state) + +def take_photo(file): + from os import system + filepath = basepath + file + + model=load_str('model') + + shutter = str(load_int('cam_shutter')) + saturation = load_str('cam_saturation') + contrast = load_str('cam_contrast') + awbg_red = load_str('cam_awbg_red') + awbg_blue = load_str('cam_awbg_blue') + gain = load_str('cam_gain') + quality = load_int('cam_jpeg_quality') + filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' + #width = load_str('cam_resx') + #height = load_str('cam_resy') + timeout = load_str('cam_timeout') + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + AF = load_bool('cam_AFmode') + camera = load_str('camera') + + + if camera == 'imx519' and AF == True: + autofocus = ' --autofocus ' + else: + autofocus = '' + + if camera == "usb_webcam": + cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 + else: + cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' + # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + + system(cmd) + return cmd + +def get_points(samples=1): + from math import pi, sqrt, acos, atan2, cos, sin + + points = [] + phi = pi * (3. - sqrt(5.)) + for i in range(int(samples)): + y = 1 - (i / float(samples - 1)) * 2 + radius = sqrt(1 - y * y) + theta = phi * i + x = cos(theta) * radius + z = sin(theta) * radius + r=sqrt(x*x+y*y+z*z) + theta_neu=acos(z/r)*180/pi + phi_neu=atan2(y,x)*180/pi + points.append((theta_neu-90,phi_neu)) + points.sort() + return points + +def create_coordinates(angle_min, angle_max,point_count): + point_count_final=point_count + if angle_max < angle_min: + a = angle_min + angle_min = angle_max + angle_max = a + point_count=point_count*90/(angle_max-angle_min) + actual_points=0 + while actual_pointsangle_min and x20: + point_count=point_count+3 + else: + point_count=point_count+1 + return filtered + + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/2024-12o/meanwhile/OpenScanStatistics.py b/update/2024-12o/meanwhile/OpenScanStatistics.py new file mode 100755 index 0000000..68005af --- /dev/null +++ b/update/2024-12o/meanwhile/OpenScanStatistics.py @@ -0,0 +1,18 @@ +import csv + +class ScanStatistics: + def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): + self.filename = filename + self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + + def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): + data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] + + with open(self.filename, "a", newline='') as csv_file: + csv_writer = csv.writer(csv_file, delimiter=';') + + # Write header if file is empty + if csv_file.tell() == 0: + csv_writer.writerow(self.header) + + csv_writer.writerow(data) diff --git a/update/2024-12o/meanwhile/config.txt b/update/2024-12o/meanwhile/config.txt new file mode 100755 index 0000000..3bb8fef --- /dev/null +++ b/update/2024-12o/meanwhile/config.txt @@ -0,0 +1,34 @@ +# For more options and information see +# http://rpf.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Additional overlays and parameters are documented /boot/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[pi4] +# Run as fast as firmware / board allows +arm_boost=1 +dtoverlay=imx519,cma-512 + +[all] +camera_auto_detect=0 +gpu_mem=256 +dtoverlay=imx519 \ No newline at end of file diff --git a/update/2024-12o/meanwhile/fla.py b/update/2024-12o/meanwhile/fla.py new file mode 100644 index 0000000..026867e --- /dev/null +++ b/update/2024-12o/meanwhile/fla.py @@ -0,0 +1,519 @@ +from flask import Flask, request, redirect, send_file, send_from_directory +from flask_restx import Resource, Api, Namespace +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont +from time import sleep, time +from OpenScan import load_int, load_float, load_bool, ringlight, motorrun +import RPi.GPIO as GPIO +from math import sqrt +import os +import math +from skimage import feature, color, transform +import numpy as np +from scipy import ndimage +import socket + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +app = Flask(__name__) +api = Api(app, version='1.0', title='OpenScan API', description='API for OpenScan') + +v1 = Namespace('v1', description='API v1') +# Create a namespace for system operations +system_ns = Namespace('system', description='System operations') +camera_ns = Namespace('camera', description='Camera operations') +motor_ns = Namespace('motor', description='Motor operations') + +api.add_namespace(v1, path='/v1') +api.add_namespace(system_ns, path='/v1/system') +api.add_namespace(camera_ns, path='/v1/camera') +api.add_namespace(motor_ns, path='/v1/motor') + +basedir = '/home/pi/OpenScan/' +timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + + +################################################################################################################### + + +@system_ns.route('/status') +class Status(Resource): + def get(self): + ''' + Get system status + ''' + import os + import json + from time import time + + if os.path.exists('/tmp/status.json'): + try: + with open('/tmp/status.json', 'r') as status_file: + status = json.load(status_file) + + elapsed_time = time() - status['start_time'] + estimated_total_time = (elapsed_time / status['current_photo']) * status['total_photos'] + time_remaining = max(0, estimated_total_time - elapsed_time) + + status.update({ + "status": "running", + "elapsed_time": int(elapsed_time), + "estimated_total_time": int(estimated_total_time), + "time_remaining": int(time_remaining) + }) + + return status, 200 + except Exception as e: + return {"error": f"Error reading status file: {str(e)}"}, 500 + else: + return {"status": "idle"}, 200 + +@system_ns.route('/shutdown') +class Shutdown(Resource): + @system_ns.doc(params={'token': 'Shutdown token for authentication'}) + def get(self): + '''Shutdown the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('shutdown -h now') + return {'message': 'Shutting down'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/reboot') +class Reboot(Resource): + @system_ns.doc(params={'token': 'Reboot token for authentication'}) + def get(self): + '''Reboot the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('reboot -h') + return {'message': 'Rebooting'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/ringlight') +class Ringlight(Resource): + @system_ns.doc(params={'state': 'Ringlight state (0 or 1)'}) + def get(self): + '''Set ringlight state''' + state = int(request.args.get('state')) + if state == 0: + ringlight(1, False) + ringlight(2, False) + else: + ringlight(1, True) + ringlight(2, True) + return {'message': f'Ringlight set to {state}'}, 200 + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) + + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') + + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wait 3-5s + return {'message': 'Auto focus triggered'}, 200 + +@motor_ns.route('/motor_run') +class MotorRun(Resource): + ''' + Run a motor + ''' + @motor_ns.doc(params={ + 'motor': 'Motor name (rotor, tt, extra)', + 'angle': 'Angle to rotate (integer)', + 'ES_enable': 'Enable endstop (optional, boolean)', + 'ES_start_state': 'Endstop start state (optional, boolean)' + }) + @motor_ns.response(400, 'Bad Request') + def get(self): + '''Run a motor''' + motor = request.args.get('motor') + if not motor: + return {'error': 'Motor parameter is required'}, 400 + if motor not in ['rotor', 'tt', 'extra']: + return {'error': 'Invalid motor name'}, 400 + + try: + angle = int(request.args.get('angle')) + except (TypeError, ValueError): + return {'error': 'Angle must be an integer'}, 400 + + ES_enable = request.args.get('ES_enable', 'false').lower() == 'true' + ES_start_state = request.args.get('ES_start_state', 'true').lower() == 'true' + + try: + motorrun(motor, angle, ES_enable, ES_start_state) + except Exception as e: + return {'error': f'Error running motor: {str(e)}'}, 500 + + return {'message': f'Motor {motor} run to {angle} degrees'}, 200 + + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + + +if __name__ == '__main__': +# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) + app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) + diff --git a/update/2024-12o/meanwhile/flows.json b/update/2024-12o/meanwhile/flows.json new file mode 100644 index 0000000..3ff3e38 --- /dev/null +++ b/update/2024-12o/meanwhile/flows.json @@ -0,0 +1,9778 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "87715429b0b1c9a3", + "type": "tab", + "label": "Statistics", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "#097479", + "value": "#097479", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#097479", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "#097479", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 8 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 7 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 5, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 8, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "ac59b8fb186de073", + "type": "ui_group", + "name": "Statistics", + "tab": "656b4eb8b15dab8f", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "656b4eb8b15dab8f", + "type": "ui_tab", + "name": "Statistics", + "icon": "dashboard", + "order": 6, + "disabled": false, + "hidden": false + }, + { + "id": "0b244f698c7ac9a2", + "type": "ui_group", + "name": "Shield Type", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "e357ef02.ef3cb", + "type": "ui_group", + "name": "Page 1", + "tab": "4bc17c6e.b74934", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "c9165343995892c6", + "type": "ui_group", + "name": "Page 2", + "tab": "", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "4bc17c6e.b74934", + "type": "ui_tab", + "name": "Page 1", + "icon": "wi-wu-tstorms", + "order": 5, + "disabled": false, + "hidden": false + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 520, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6", + "d81572486f15cd7a" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244", + "9b3e6a06c82a0f52", + "fbc5fc2e65311f8b" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 440, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 440, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "65518f3d4e3095e5", + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 650, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 600, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 680, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 600, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 640, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 720, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 820, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 820, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "1dffb799fdf10cbc", + "2afb6a45c73fa244", + "2e9b29c70969cf01", + "2f4c0f98.dee2", + "3876d5cbd248592b", + "592ec13d8f8923a9", + "5e7d5e4335d37794", + "9b3e6a06c82a0f52", + "9fd259de91de1da1", + "b4c843620c251c43", + "cb40b9341bd22a28", + "d0104e0163745993", + "d1efcd5fa9d25785", + "da61581182b7299e", + "e5f38b4a07a5e278", + "fbc5fc2e65311f8b", + "fd0258418489839d" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "e548168473aa85d6", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 5, + "width": 0, + "height": 0, + "passthru": false, + "label": "Statistics", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "5", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 100, + "y": 760, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "d81572486f15cd7a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "loadl", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = String(data);\nflow.set('openscan_version',data)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 360, + "wires": [ + [ + "b1b0ccb783dd5882" + ] + ] + }, + { + "id": "fa6db57803ae2b6d", + "type": "debug", + "z": "e6f4d02efb300ea9", + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": true, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 590, + "y": 460, + "wires": [] + }, + { + "id": "b1b0ccb783dd5882", + "type": "change", + "z": "e6f4d02efb300ea9", + "name": "openscan_version", + "rules": [ + { + "t": "set", + "p": "openscan_version", + "pt": "flow", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 415, + "y": 360, + "wires": [ + [ + "fa6db57803ae2b6d", + "0d8c6bc7887fb3c2" + ] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/v1/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/v1/camera/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 535, + "y": 1060, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/v1/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2100, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2340, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2140, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2180, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2060, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2140, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2180, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2100, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2060, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2380, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2420, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2300, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2420, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2300, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2380, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2340, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2220, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2220, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2460, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2500, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2260, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2500, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2260, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2600, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2640, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2760, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2800, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2600, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2640, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2760, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2800, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2840, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3000, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3000, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3040, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3040, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3120, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3120, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3160, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3160, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3240, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3240, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3280, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3280, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3320, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3320, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2880, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2600, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2600, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2760, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2800, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3000, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3000, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3040, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3040, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 7, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88", + "53681e53353db898" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2560, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2560, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 560, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 560, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 450, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 540, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, + { + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "e98c1b83744bb863", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Delete Aborted", + "tooltip": "Delete aborted photosets", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 420, + "y": 520, + "wires": [ + [ + "7438a5bf5fcddec4" + ] + ] + }, + { + "id": "7438a5bf5fcddec4", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "delete_aborted", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('delete_aborted'):\n save('delete_aborted', state)\n", + "outputs": 1, + "x": 600, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "53681e53353db898", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'delete_aborted'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 520, + "wires": [ + [ + "e98c1b83744bb863" + ] + ] + }, + { + "id": "48386fdb54980ec7", + "type": "comment", + "z": "e43a27722b508115", + "name": "Shield", + "info": "", + "x": 90, + "y": 3760, + "wires": [] + }, + { + "id": "fbc5fc2e65311f8b", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3840, + "wires": [ + [ + "5618e266f6966ae6" + ] + ] + }, + { + "id": "5618e266f6966ae6", + "type": "function", + "z": "e43a27722b508115", + "name": "loadl", + "func": "var file = 'shield_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 170, + "y": 3840, + "wires": [ + [ + "c97113d841391e40" + ] + ] + }, + { + "id": "c97113d841391e40", + "type": "ui_dropdown", + "z": "e43a27722b508115", + "name": "", + "label": "Shield Type", + "tooltip": "", + "place": "Select option", + "group": "0b244f698c7ac9a2", + "order": 0, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "Green", + "value": "green", + "type": "str" + }, + { + "label": "Black", + "value": "black", + "type": "str" + } + ], + "payload": "", + "topic": "payload", + "topicType": "msg", + "className": "", + "x": 310, + "y": 3840, + "wires": [ + [ + "2b639346c1b56578" + ] + ] + }, + { + "id": "2b639346c1b56578", + "type": "function", + "z": "e43a27722b508115", + "name": "function 2", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'shield_type';\nconst current_shield = fs.readFileSync(filepath + file, 'utf8');\n\nvar current_choice = msg.payload\n\nif (current_choice != current_shield) {\n \n switch (current_choice) {\n case \"green\":\n fs.writeFile(filepath + 'pin_external', String(10), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(17), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(9), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(11), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(14), err => {\n if (err) {\n return\n }\n });\n break;\n case \"black\":\n fs.writeFile(filepath + 'pin_external', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(24), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(22), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(17), err => {\n if (err) {\n return\n }\n });\n break;\n case \"custom\":\n break;\n }\n\n fs.writeFile(filepath + file, current_choice, err => {\n if (err) {\n return\n }\n });\n}\n\nmsg.status = 'The new ' + current_choice + ' shield has been configured'\nmsg.topic = msg.status\nmsg.payload = 'Do you want to reboot now to apply the changes?'\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 460, + "y": 3840, + "wires": [ + [ + "137f032887544d74" + ] + ] + }, + { + "id": "137f032887544d74", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": false, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Reboot", + "x": 600, + "y": 3840, + "wires": [ + [ + "d2db49796fe0da79", + "d0d6820224b0ab0f" + ] + ] + }, + { + "id": "d2db49796fe0da79", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "print(msg['payload'])\nreturn msg", + "outputs": 1, + "x": 790, + "y": 3840, + "wires": [ + [] + ] + }, + { + "id": "d0d6820224b0ab0f", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 3720, + "wires": [] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = load_str('openscan_version')\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 9, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "9b3e6a06c82a0f52", + "type": "link in", + "z": "87715429b0b1c9a3", + "name": "", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 55, + "y": 120, + "wires": [ + [ + "f128ca405d1e1e4d", + "07d7ce3dab5f1c11" + ] + ] + }, + { + "id": "cd0dc08fcb5968c8", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Successful Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 450, + "y": 180, + "wires": [] + }, + { + "id": "f128ca405d1e1e4d", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Successful Scans", + "x": 210, + "y": 180, + "wires": [ + [ + "cd0dc08fcb5968c8" + ], + [], + [] + ] + }, + { + "id": "b91b4d65f2090793", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Aborted Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 440, + "y": 120, + "wires": [] + }, + { + "id": "07d7ce3dab5f1c11", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Aborted Scans", + "x": 200, + "y": 120, + "wires": [ + [ + "b91b4d65f2090793" + ], + [], + [] + ] + }, + { + "id": "5b3aa9a71591ba34", + "type": "comment", + "z": "87715429b0b1c9a3", + "name": "Statistics", + "info": "", + "x": 100, + "y": 40, + "wires": [] + } +] \ No newline at end of file diff --git a/update/2024-12o/meanwhile/settings.js b/update/2024-12o/meanwhile/settings.js new file mode 100644 index 0000000..357b02b --- /dev/null +++ b/update/2024-12o/meanwhile/settings.js @@ -0,0 +1,512 @@ +/** + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT + * + * It can contain any valid JavaScript code that will get run when Node-RED + * is started. + * + * Lines that start with // are commented out. + * Each entry should be separated from the entries above and below by a comma ',' + * + * For more information about individual settings, refer to the documentation: + * https://nodered.org/docs/user-guide/runtime/configuration + * + * The settings are split into the following sections: + * - Flow File and User Directory Settings + * - Security + * - Server Settings + * - Runtime Settings + * - Editor Settings + * - Node Settings + * + **/ +process.env.HOSTNAME = require('os').hostname(); + +module.exports = { + +/******************************************************************************* + * Flow File and User Directory Settings + * - flowFile + * - credentialSecret + * - flowFilePretty + * - userDir + * - nodesDir + ******************************************************************************/ + + /** The file containing the flows. If not set, defaults to flows_.json **/ + flowFile: "flows.json", + + /** By default, credentials are encrypted in storage using a generated key. To + * specify your own secret, set the following property. + * If you want to disable encryption of credentials, set this property to false. + * Note: once you set this property, do not change it - doing so will prevent + * node-red from being able to decrypt your existing credentials and they will be + * lost. + */ + credentialSecret: false, + + /** By default, the flow JSON will be formatted over multiple lines making + * it easier to compare changes when using version control. + * To disable pretty-printing of the JSON set the following property to false. + */ + flowFilePretty: true, + + /** By default, all user data is stored in a directory called `.node-red` under + * the user's home directory. To use a different location, the following + * property can be used + */ + //userDir: '/home/nol/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + + /** Node-RED scans the `nodes` directory in the userDir to find local node files. + * The following property can be used to specify an additional directory to scan. + */ + //nodesDir: '/home/nol/.node-red/nodes', + +/******************************************************************************* + * Security + * - adminAuth + * - https + * - httpsRefreshInterval + * - requireHttps + * - httpNodeAuth + * - httpStaticAuth + ******************************************************************************/ + + /** To password protect the Node-RED editor and admin API, the following + * property can be used. See http://nodered.org/docs/security.html for details. + */ + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + /** The following property can be used to enable HTTPS + * This property can be either an object, containing both a (private) key + * and a (public) certificate, or a function that returns such an object. + * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + * for details of its contents. + */ + + /** Option 1: static object */ + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + + /** Option 2: function that returns the HTTP configuration object */ + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + /** If the `https` setting is a function, the following setting can be used + * to set how often, in hours, the function will be called. That can be used + * to refresh any certificates. + */ + //httpsRefreshInterval : 12, + + /** The following property can be used to cause insecure HTTP connections to + * be redirected to HTTPS. + */ + //requireHttps: true, + + /** To password protect the node-defined HTTP endpoints (httpNodeRoot), + * including node-red-dashboard, or the static content (httpStatic), the + * following properties can be used. + * The `pass` field is a bcrypt hash of the password. + * See http://nodered.org/docs/security.html#generating-the-password-hash + */ + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + +/******************************************************************************* + * Server Settings + * - uiPort + * - uiHost + * - apiMaxLength + * - httpServerOptions + * - httpAdminRoot + * - httpAdminMiddleware + * - httpNodeRoot + * - httpNodeCors + * - httpNodeMiddleware + * - httpStatic + * - httpStaticRoot + ******************************************************************************/ + + /** the tcp port that the Node-RED web server is listening on */ + uiPort: process.env.PORT || 80, + + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. + * To listen on all IPv6 addresses, set uiHost to "::", + * The following property can be used to listen on a specific interface. For + * example, the following would only allow connections from the local machine. + */ + //uiHost: "127.0.0.1", + + /** The maximum size of HTTP request that will be accepted by the runtime api. + * Default: 5mb + */ + //apiMaxLength: '5mb', + + /** The following property can be used to pass custom options to the Express.js + * server used by Node-RED. For a full list of available options, refer + * to http://expressjs.com/en/api.html#app.settings.table + */ + //httpServerOptions: { }, + + /** By default, the Node-RED UI is available at http://localhost:1880/ + * The following property can be used to specify a different root path. + * If set to false, this is disabled. + */ + httpAdminRoot: '/editor', + + /** The following property can be used to add a custom middleware function + * in front of all admin http routes. For example, to set custom http + * headers. It can be a single function or an array of middleware functions. + */ + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + + /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. + * By default, these are served relative to '/'. The following property + * can be used to specifiy a different root path. If set to false, this is + * disabled. + */ + //httpNodeRoot: '/red-nodes', + + /** The following property can be used to configure cross-origin resource sharing + * in the HTTP nodes. + * See https://github.com/troygoode/node-cors#configuration-options for + * details on its contents. The following is a basic permissive set of options: + */ + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + /** If you need to set an http proxy please set an environment variable + * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + * For example - http_proxy=http://myproxy.com:8080 + * (Setting it here will have no effect) + * You may also specify no_proxy (or NO_PROXY) to supply a comma separated + * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + */ + + /** The following property can be used to add a custom middleware function + * in front of all http in nodes. This allows custom authentication to be + * applied to all http in nodes, or any other sort of common request processing. + * It can be a single function or an array of middleware functions. + */ + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + /** When httpAdminRoot is used to move the UI to a different root path, the + * following property can be used to identify a directory of static content + * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot + */ + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + +/******************************************************************************* + * Runtime Settings + * - lang + * - logging + * - contextStorage + * - exportGlobalContextKeys + * - externalModules + ******************************************************************************/ + + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", + + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, + + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, + + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: [], + // denyList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, + + +/******************************************************************************* + * Editor Settings + * - disableEditor + * - editorTheme + ******************************************************************************/ + + /** The following property can be used to disable the editor. The admin API + * is not affected by this option. To disable both the editor and the admin + * API, use either the httpRoot or httpAdminRoot properties + */ + //disableEditor: false, + + /** Customising the editor + * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes + * for all available options. + */ + editorTheme: { + /** The following property can be used to set a custom theme for the editor. + * See https://github.com/node-red-contrib-themes/theme-collection for + * a collection of themes to chose from. + */ + //theme: "", + palette: { + /** The following property can be used to order the categories in the editor + * palette. If a node's category is not in the list, the category will get + * added to the end of the palette. + * If not set, the following default order is used: + */ + //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + }, + projects: { + /** To enable the Projects feature, set this value to true */ + enabled: false, + workflow: { + /** Set the default projects workflow mode. + * - manual - you must manually commit changes + * - auto - changes are automatically committed + * This can be overridden per-user from the 'Git config' + * section of 'User Settings' within the editor + */ + mode: "manual" + } + }, + codeEditor: { + /** Select the text editor component used by the editor. + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired + */ + lib: "monaco", + options: { + /** The follow options only apply if the editor is set to "monaco" + * + * theme - must match the file name of a theme in + * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme + * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" + */ + theme: "vs", + /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html + */ + //fontSize: 14, + //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", + //fontLigatures: true, + } + } + }, + +/******************************************************************************* + * Node Settings + * - fileWorkingDirectory + * - functionGlobalContext + * - functionExternalModules + * - nodeMessageBufferMaxLength + * - ui (for use with Node-RED Dashboard) + * - debugUseColors + * - debugMaxLength + * - execMaxBufferSize + * - httpRequestTimeout + * - mqttReconnectTime + * - serialReconnectTime + * - socketReconnectTime + * - socketTimeout + * - tcpMsgQueueSize + * - inboundWebSocketTimeout + * - tlsConfigDisableLocalFiles + * - webSocketNodeVerifyClient + ******************************************************************************/ + + /** The working directory to handle relative file paths from within the File nodes + * defaults to the working directory of the Node-RED process. + */ + //fileWorkingDirectory: "", + + /** Allow the Function node to load additional npm modules directly */ + functionExternalModules: true, + + /** The following property can be used to set predefined values in Global Context. + * This allows extra node modules to be made available with in Function node. + * For example, the following: + * functionGlobalContext: { os:require('os') } + * will allow the `os` module to be accessed in a Function node using: + * global.get("os") + */ +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, + /** The maximum number of messages nodes will buffer internally as part of their + * operation. This applies across a range of nodes that operate on message sequences. + * defaults to no limit. A value of 0 also means no limit is applied. + */ + //nodeMessageBufferMaxLength: 0, + + /** If you installed the optional node-red-dashboard you can set it's path + * relative to httpNodeRoot + * Other optional properties include + * readOnly:{boolean}, + * middleware:{function or array}, (req,res,next) - http middleware + * ioMiddleware:{function or array}, (socket,next) - socket.io middleware + */ + ui: { path: "" }, + + /** Colourise the console output of the debug node */ + //debugUseColors: true, + + /** The maximum length, in characters, of any message sent to the debug sidebar tab */ + debugMaxLength: 1000, + + /** Maximum buffer size for the exec node. Defaults to 10Mb */ + //execMaxBufferSize: 10000000, + + /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ + //httpRequestTimeout: 120000, + + /** Retry time in milliseconds for MQTT connections */ + mqttReconnectTime: 15000, + + /** Retry time in milliseconds for Serial port connections */ + serialReconnectTime: 15000, + + /** Retry time in milliseconds for TCP socket connections */ + //socketReconnectTime: 10000, + + /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ + //socketTimeout: 120000, + + /** Maximum number of messages to wait in queue while attempting to connect to TCP socket + * defaults to 1000 + */ + //tcpMsgQueueSize: 2000, + + /** Timeout in milliseconds for inbound WebSocket connections that do not + * match any configured node. Defaults to 5000 + */ + //inboundWebSocketTimeout: 5000, + + /** To disable the option for using local files for storing keys and + * certificates in the TLS configuration node, set this to true. + */ + //tlsConfigDisableLocalFiles: true, + + /** The following property can be used to verify websocket connection attempts. + * This allows, for example, the HTTP request headers to be checked to ensure + * they include valid authentication information. + */ + //webSocketNodeVerifyClient: function(info) { + // /** 'info' has three properties: + // * - origin : the value in the Origin header + // * - req : the HTTP request + // * - secure : true if req.connection.authorized or req.connection.encrypted is set + // * + // * The function should return true if the connection should be accepted, false otherwise. + // * + // * Alternatively, if this function is defined to accept a second argument, callback, + // * it can be used to verify the client asynchronously. + // * The callback takes three arguments: + // * - result : boolean, whether to accept the connection or not + // * - code : if result is false, the HTTP error status to return + // * - reason: if result is false, the HTTP reason string to return + // */ + //}, +} diff --git a/update/2024-12o/scripts/expand_root.sh b/update/2024-12o/scripts/expand_root.sh new file mode 100755 index 0000000..f4f7148 --- /dev/null +++ b/update/2024-12o/scripts/expand_root.sh @@ -0,0 +1,7 @@ +#!/bin/bash +if test -f "/boot/expand_root"; then + echo "expanding root partition" + raspi-config --expand-rootfs + rm -fr /boot/expand_root + shutdown -r now +fi diff --git a/update/2024-12o/scripts/startup.sh b/update/2024-12o/scripts/startup.sh new file mode 100755 index 0000000..bdaad1d --- /dev/null +++ b/update/2024-12o/scripts/startup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +settings_folder="/home/pi/OpenScan/settings" + +# Generate an unique UUID that identifies that OpenScan + +if [ ! -f $settings_folder/openscan_uuid ]; then + echo $(cat /proc/sys/kernel/random/uuid) > $settings_folder/openscan_uuid +fi +echo `cat /proc/cpuinfo|grep Model|cut -d: -f2|awk '{$1=$1};1'` > $settings_folder/architecture +echo `libcamera-still --list-cameras|head -3|tail -1|cut -d: -f2|cut -d[ -f1|awk '{$1=$1};1'` > $settings_folder/camera diff --git a/update/2024-12o/stable/OpenScan.py b/update/2024-12o/stable/OpenScan.py new file mode 100644 index 0000000..e634511 --- /dev/null +++ b/update/2024-12o/stable/OpenScan.py @@ -0,0 +1,312 @@ +basepath = '/home/pi/OpenScan/' +from os.path import isfile +import os + +def load_bool(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + if value == '1' or value == 'True' or value =='true': + value = True + else: + value = False + return value + +def fade_led(pin_led, fade_steps, duty_max, dir = True): + import RPi.GPIO as GPIO + import time + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(pin_led, GPIO.OUT) + pwm = GPIO.PWM(pin_led, 200) + + if dir: + pwm.start(0) + for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + else: + pwm.start(duty_max) + for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps + pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) + time.sleep(0.001) # Pause between steps (adjust as needed) + pwm.stop() + + +def check_hotspot_mode(interface="wlan0"): + import subprocess + try: + output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") + if "Mode:Master" in output: + return True + elif "Mode:Managed" in output: + return False + else: + return False + except subprocess.CalledProcessError as e: + return False + + + +def add_wifi_network(ssid, password, country): + import re + conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + + if not os.path.exists(conf_file): + return False + + if not (ssid and password and country): + return False + + with open(conf_file, "r") as f: + content = f.read() + + updated_content = re.sub(r'country=\w+', f'country={country}', content) + + if f'ssid="{ssid}"' in content: + network_block_pattern = re.compile( + r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL + ) + updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' + updated_content = network_block_pattern.sub(updated_network_block, updated_content) + else: + network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' + updated_content += network_block + + with open(conf_file, "w") as f: + f.write(updated_content) + os.system("sudo systemctl restart wpa_supplicant@wlan0") + return True + +def load_str(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = file.read().replace('\n','') + return value + +def load_int(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = int(file.read().replace('\n','')) + return value + +def load_float(name): + filename = basepath+'settings/'+name + if not isfile(filename): + return + with open(filename, 'r') as file: + value = float(file.read().replace('\n','')) + return value + +def save(name, value): + filename = basepath+'settings/'+name + with open(filename, 'w+') as file: + file.write(str(value)) + return + +def OpenScanCloud(cmd, msg): + from requests import get + osc_user = 'openscan' + osc_pw = 'free' + osc_server = 'http://openscanfeedback.dnsuser.de:1334/' + + try: + r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) + except: + r = type('obj', (object,), {'status_code' : 404, 'text':None}) + return r + +def camera(cmd, msg = {}): + from requests import get + flask = 'http://127.0.0.1:1312/' + try: + r = get(flask + cmd, params=msg) + return r.status_code + except: + return 400 + +def motorrun(motor,angle,ES_enable=False,ES_start_state = True): + #motor can be "rotor", "tt" or "extra" + import RPi.GPIO as GPIO + from time import sleep + from math import cos + msg = {'cmd':'set'} + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + + spr = load_int(motor + '_stepsperrotation') + dirpin = load_int('pin_' + motor + '_dir') + steppin = load_int('pin_' + motor +'_step') + ES_pin = load_int('pin_' + motor + '_endstop') + dir = load_int(motor + '_dir') + ramp = load_int(motor + '_accramp') + acc = load_float(motor + '_acc') + delay_init = load_float(motor + '_delay') + delay = delay_init + + step_count=int(angle*spr/360) * dir + GPIO.setup(dirpin, GPIO.OUT) + GPIO.setup(steppin, GPIO.OUT) + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + + if (step_count>0): + GPIO.output(dirpin, GPIO.HIGH) + if(step_count<0): + GPIO.output(dirpin, GPIO.LOW) + step_count=-step_count + for x in range(step_count): + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + i = 0 + while i <= 10: + if GPIO.input(ES_pin) == ES_start_state: + i = 11 + if i == 10: + return + i = i + 1 + + GPIO.output(steppin, GPIO.HIGH) + if x<=ramp and x<=step_count/2: + delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) + #delay=delay_init+(ramp-x)*(delay_init)/acc + elif step_count-x<=ramp and x>step_count/2: + delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) + #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc + else: + delay = delay_init + sleep(delay) + GPIO.output(steppin, GPIO.LOW) + sleep(delay) + +def ringlight(number,state): + import RPi.GPIO as GPIO + msg = {'cmd':'set'} + pin = load_int('pin_ringlight' + str(number)) + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(pin, GPIO.OUT) + GPIO.output(pin, state) + +def take_photo(file): + from os import system + filepath = basepath + file + + model=load_str('model') + + shutter = str(load_int('cam_shutter')) + saturation = load_str('cam_saturation') + contrast = load_str('cam_contrast') + awbg_red = load_str('cam_awbg_red') + awbg_blue = load_str('cam_awbg_blue') + gain = load_str('cam_gain') + quality = load_int('cam_jpeg_quality') + filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' + #width = load_str('cam_resx') + #height = load_str('cam_resy') + timeout = load_str('cam_timeout') + cropx = load_int('cam_cropx')/200 + cropy = load_int('cam_cropy')/200 + rotation = load_int('cam_rotation') + AF = load_bool('cam_AFmode') + camera = load_str('camera') + + + if camera == 'imx519' and AF == True: + autofocus = ' --autofocus ' + else: + autofocus = '' + + if camera == "usb_webcam": + cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 + else: + cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + ' >/dev/null 2>&1' + # cmd = 'libcamera-still -n --denoise off --sharpness 0 -o ' + filepath2 + ' -t ' + timeout +' --shutter ' + shutter + ' --saturation ' + saturation + ' --contrast ' + contrast + ' --awbgains '+awbg_red + "," + awbg_blue + ' --gain ' + gain + ' -q ' + str(quality) + autofocus + + system(cmd) + return cmd + +def get_points(samples=1): + from math import pi, sqrt, acos, atan2, cos, sin + + points = [] + phi = pi * (3. - sqrt(5.)) + for i in range(int(samples)): + y = 1 - (i / float(samples - 1)) * 2 + radius = sqrt(1 - y * y) + theta = phi * i + x = cos(theta) * radius + z = sin(theta) * radius + r=sqrt(x*x+y*y+z*z) + theta_neu=acos(z/r)*180/pi + phi_neu=atan2(y,x)*180/pi + points.append((theta_neu-90,phi_neu)) + points.sort() + return points + +def create_coordinates(angle_min, angle_max,point_count): + point_count_final=point_count + if angle_max < angle_min: + a = angle_min + angle_min = angle_max + angle_max = a + point_count=point_count*90/(angle_max-angle_min) + actual_points=0 + while actual_pointsangle_min and x20: + point_count=point_count+3 + else: + point_count=point_count+1 + return filtered + + +def haversine_distance_deg(theta1, phi1, theta2, phi2): + import numpy as np + R = 1 + dtheta = np.radians(theta2 - theta1) + dphi = np.radians(phi2 - phi1) + + theta1, phi1 = np.radians(theta1), np.radians(phi1) + theta2, phi2 = np.radians(theta2), np.radians(phi2) + + a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + +def sort_spherical_coordinates_deg(points_spherical_deg): + import numpy as np + from tsp_solver.greedy import solve_tsp + + points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array + + n = len(points_spherical_deg) + dist_matrix = np.zeros((n, n)) + + # Calculate haversine distance for each pair of points + for i in range(n): + for j in range(i + 1, n): + dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], + points_spherical_deg[j, 0], points_spherical_deg[j, 1]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Solve the TSP problem using the tsp_solver.greedy algorithm + path = solve_tsp(dist_matrix) + + sorted_points_spherical_deg = points_spherical_deg[path] + + # Convert the sorted NumPy array back to a list of tuples + return [tuple(point) for point in sorted_points_spherical_deg] diff --git a/update/2024-12o/stable/OpenScanStatistics.py b/update/2024-12o/stable/OpenScanStatistics.py new file mode 100755 index 0000000..68005af --- /dev/null +++ b/update/2024-12o/stable/OpenScanStatistics.py @@ -0,0 +1,18 @@ +import csv + +class ScanStatistics: + def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): + self.filename = filename + self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + + def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): + data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] + + with open(self.filename, "a", newline='') as csv_file: + csv_writer = csv.writer(csv_file, delimiter=';') + + # Write header if file is empty + if csv_file.tell() == 0: + csv_writer.writerow(self.header) + + csv_writer.writerow(data) diff --git a/update/2024-12o/stable/config.txt b/update/2024-12o/stable/config.txt new file mode 100755 index 0000000..4faf2c8 --- /dev/null +++ b/update/2024-12o/stable/config.txt @@ -0,0 +1,84 @@ +# For more options and information see +# http://rpf.io/configtxt +# Some settings may impact device functionality. See link above for details + +# uncomment if you get no picture on HDMI for a default "safe" mode +#hdmi_safe=1 + +# uncomment the following to adjust overscan. Use positive numbers if console +# goes off screen, and negative if there is too much border +#overscan_left=16 +#overscan_right=16 +#overscan_top=16 +#overscan_bottom=16 + +# uncomment to force a console size. By default it will be display's size minus +# overscan. +#framebuffer_width=1280 +#framebuffer_height=720 + +# uncomment if hdmi display is not detected and composite is being output +#hdmi_force_hotplug=1 + +# uncomment to force a specific HDMI mode (this will force VGA) +#hdmi_group=1 +#hdmi_mode=1 + +# uncomment to force a HDMI mode rather than DVI. This can make audio work in +# DMT (computer monitor) modes +#hdmi_drive=2 + +# uncomment to increase signal to HDMI, if you have interference, blanking, or +# no display +#config_hdmi_boost=4 + +# uncomment for composite PAL +#sdtv_mode=2 + +#uncomment to overclock the arm. 700 MHz is the default. +#arm_freq=800 + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Uncomment this to enable infrared communication. +#dtoverlay=gpio-ir,gpio_pin=17 +#dtoverlay=gpio-ir-tx,gpio_pin=18 + +# Additional overlays and parameters are documented /boot/overlays/README + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Disable compensation for displays with overscan +disable_overscan=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] + +[pi4] +# Run as fast as firmware / board allows +arm_boost=1 + +[all] +camera_auto_detect=0 +gpu_mem=256 +dtoverlay=imx519 +#dtoverlay=imx519,media-controller=1 diff --git a/update/2024-12o/stable/fla.py b/update/2024-12o/stable/fla.py new file mode 100644 index 0000000..026867e --- /dev/null +++ b/update/2024-12o/stable/fla.py @@ -0,0 +1,519 @@ +from flask import Flask, request, redirect, send_file, send_from_directory +from flask_restx import Resource, Api, Namespace +from picamera2 import Picamera2 +from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont +from time import sleep, time +from OpenScan import load_int, load_float, load_bool, ringlight, motorrun +import RPi.GPIO as GPIO +from math import sqrt +import os +import math +from skimage import feature, color, transform +import numpy as np +from scipy import ndimage +import socket + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +app = Flask(__name__) +api = Api(app, version='1.0', title='OpenScan API', description='API for OpenScan') + +v1 = Namespace('v1', description='API v1') +# Create a namespace for system operations +system_ns = Namespace('system', description='System operations') +camera_ns = Namespace('camera', description='Camera operations') +motor_ns = Namespace('motor', description='Motor operations') + +api.add_namespace(v1, path='/v1') +api.add_namespace(system_ns, path='/v1/system') +api.add_namespace(camera_ns, path='/v1/camera') +api.add_namespace(motor_ns, path='/v1/motor') + +basedir = '/home/pi/OpenScan/' +timer = time() +cam_mode = 0 +hostname = socket.gethostname().split(":") + +def overlay_mask(image, mask_image): + # Ensure image is in RGB mode + image_rgb = image.convert('RGB') + # Create an empty image with RGBA channels + overlay = Image.new('RGBA', image_rgb.size) + + # Prepare a red image of the same size + red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) + # Prepare a mask where the condition is met (mask_image pixels == 255) + mask_condition = np.array(mask_image) > 0 + overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) + # Paste the red image onto the overlay using the condition mask + overlay.paste(red_image, mask=overlay_mask) + # Combine the original image with the overlay + combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) + # Convert the final image to RGB + combined_rgb = combined.convert('RGB') + return combined_rgb + + +def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): + + # Convert PIL image to grayscale + image_gray = image.convert('L') + + # Convert grayscale image to numpy array + image_array = np.array(image_gray) + + # Calculate the gradient using a Sobel filter + dx = ndimage.sobel(image_array, 0) # horizontal derivative + dy = ndimage.sobel(image_array, 1) # vertical derivative + mag = np.hypot(dx, dy) # magnitude + + # Threshold the gradient to create a mask of the sharpest areas + mask = np.where(mag > threshold, 255, 0).astype(np.uint8) + + dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) + # Create a PIL image from the mask + mask_image = Image.fromarray(dilated_mask) + + return mask_image + + + + +################################################################################################################### + + +@system_ns.route('/status') +class Status(Resource): + def get(self): + ''' + Get system status + ''' + import os + import json + from time import time + + if os.path.exists('/tmp/status.json'): + try: + with open('/tmp/status.json', 'r') as status_file: + status = json.load(status_file) + + elapsed_time = time() - status['start_time'] + estimated_total_time = (elapsed_time / status['current_photo']) * status['total_photos'] + time_remaining = max(0, estimated_total_time - elapsed_time) + + status.update({ + "status": "running", + "elapsed_time": int(elapsed_time), + "estimated_total_time": int(estimated_total_time), + "time_remaining": int(time_remaining) + }) + + return status, 200 + except Exception as e: + return {"error": f"Error reading status file: {str(e)}"}, 500 + else: + return {"status": "idle"}, 200 + +@system_ns.route('/shutdown') +class Shutdown(Resource): + @system_ns.doc(params={'token': 'Shutdown token for authentication'}) + def get(self): + '''Shutdown the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('shutdown -h now') + return {'message': 'Shutting down'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/reboot') +class Reboot(Resource): + @system_ns.doc(params={'token': 'Reboot token for authentication'}) + def get(self): + '''Reboot the Raspberry Pi''' + shutdown_token = request.args.get('token') + hostname = request.host.split(":")[0] + with open("/home/pi/OpenScan/settings/session_token", "r") as f: + session_token = f.readline()[:20] + + if shutdown_token == session_token or True: + delay = 0.1 + ringlight(2, False) + + for _ in range(5): + ringlight(1, True) + sleep(delay) + ringlight(1, False) + sleep(delay) + + os.system('reboot -h') + return {'message': 'Rebooting'}, 200 + else: + return redirect("http://" + hostname, code=302) + +@system_ns.route('/ringlight') +class Ringlight(Resource): + @system_ns.doc(params={'state': 'Ringlight state (0 or 1)'}) + def get(self): + '''Set ringlight state''' + state = int(request.args.get('state')) + if state == 0: + ringlight(1, False) + ringlight(2, False) + else: + ringlight(1, True) + ringlight(2, True) + return {'message': f'Ringlight set to {state}'}, 200 + +def plot_orb_keypoints(pil_image): + downscale = 2 + # Read the image from the given image path + image = np.array(pil_image) + #image = io.imread(image_path) + image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) + + # Convert the image to grayscale + gray_image = color.rgb2gray(image) + + try: + orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) + orb.detect_and_extract(gray_image) + keypoints = orb.keypoints + except: + return pil_image + + # Convert the image back to the range [0, 255] + display_image = (image * 255).astype(np.uint8) + + # Draw the keypoints on the image + draw = ImageDraw.Draw(pil_image) + size = max(2,int(image.shape[0]*downscale*0.005)) + for i, (y, x) in enumerate(keypoints): + draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) + # Save the image with keypoints to the given output path + return pil_image + +def add_histo(img): + histo_size = 241 + + img_gray = ImageOps.grayscale(img) + histogram = img_gray.histogram() + histogram_log = [math.log10(h + 1) for h in histogram] + histogram_max = max(histogram_log) + histogram_normalized = [float(h) / histogram_max for h in histogram_log] + hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) + draw = ImageDraw.Draw(hist_image) + + for i in range(0, 256): + x = i + y = 256 - int(histogram_normalized[i] * 256) + draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) + + text = "" + if min(histogram[235:238])>0: + text = "overexposed" + if sum(histogram[190:192])<8: + text = "underexposed" + font = ImageFont.truetype("DejaVuSans.ttf", 30) + + bbox = draw.textbbox((0, 0), text, font=font) + + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + + x = (hist_image.width - text_width )/2 + y = hist_image.height - text_height - 10 + draw.text((x, y), text, font=font, fill=(255,0,0)) + + scale = 0.25 + width1, height1 = hist_image.size + width2 = img.size[0] + new_width1 = int(width2 * scale) + new_height1 = int((height1 / width1) * new_width1) + hist_image = hist_image.convert('RGB') + + hist_image = hist_image.resize((new_width1, new_height1)) + x = hist_image.width - text_width - 10 + y = hist_image.height - text_height - 10 + + + img.paste(hist_image, (img.size[0]-new_width1-int(0.01*img.size[0]),img.size[1]-new_height1-int(0.01*img.size[0]))) + + return img + +def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: + threshold = load_int("cam_mask_threshold") + if threshold <= 1: + return image + orig = image + image = image.resize((int(image.width*scale),int(image.height*scale))) + image = image.convert("L") + reduced = image + image = image.filter(ImageFilter.EDGE_ENHANCE) + image = image.filter(ImageFilter.BLUR) + reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) + mask = ImageChops.difference(image, reduced) + mask = ImageEnhance.Brightness(mask).enhance(2.5) + mask = mask.filter(ImageFilter.MaxFilter(9)) + mask = mask.filter(ImageFilter.MinFilter(5)) + mask = mask.point(lambda x: 255 if x wait 3-5s + return {'message': 'Auto focus triggered'}, 200 + +@motor_ns.route('/motor_run') +class MotorRun(Resource): + ''' + Run a motor + ''' + @motor_ns.doc(params={ + 'motor': 'Motor name (rotor, tt, extra)', + 'angle': 'Angle to rotate (integer)', + 'ES_enable': 'Enable endstop (optional, boolean)', + 'ES_start_state': 'Endstop start state (optional, boolean)' + }) + @motor_ns.response(400, 'Bad Request') + def get(self): + '''Run a motor''' + motor = request.args.get('motor') + if not motor: + return {'error': 'Motor parameter is required'}, 400 + if motor not in ['rotor', 'tt', 'extra']: + return {'error': 'Invalid motor name'}, 400 + + try: + angle = int(request.args.get('angle')) + except (TypeError, ValueError): + return {'error': 'Angle must be an integer'}, 400 + + ES_enable = request.args.get('ES_enable', 'false').lower() == 'true' + ES_start_state = request.args.get('ES_start_state', 'true').lower() == 'true' + + try: + motorrun(motor, angle, ES_enable, ES_start_state) + except Exception as e: + return {'error': f'Error running motor: {str(e)}'}, 500 + + return {'message': f'Motor {motor} run to {angle} degrees'}, 200 + + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + + +if __name__ == '__main__': +# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) + app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) + diff --git a/update/2024-12o/stable/flows.json b/update/2024-12o/stable/flows.json new file mode 100644 index 0000000..3f68979 --- /dev/null +++ b/update/2024-12o/stable/flows.json @@ -0,0 +1,9378 @@ +[ + { + "id": "e6f4d02efb300ea9", + "type": "tab", + "label": "Init", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "481edaf6db5a7a54", + "type": "tab", + "label": "Scan", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "80a3942785a26c29", + "type": "tab", + "label": "Files", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e43a27722b508115", + "type": "tab", + "label": "Settings", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a5557543ccff5889", + "type": "tab", + "label": "Update", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "90223f7ddc082321", + "type": "ui_group", + "name": "preview", + "tab": "e23b837a9f040895", + "order": 2, + "disp": false, + "width": "7", + "collapse": false, + "className": "" + }, + { + "id": "e23b837a9f040895", + "type": "ui_tab", + "name": "Scan", + "icon": "dashboard", + "order": 2, + "disabled": false, + "hidden": false + }, + { + "id": "5c06cb6bcc371ee6", + "type": "ui_base", + "theme": { + "name": "theme-dark", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "reset": false + }, + "themeState": { + "base-color": { + "default": "#097479", + "value": "#097479", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#097479", + "edited": false + }, + "page-backgroundColor": { + "value": "#111111", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#0eb8c0", + "edited": false + }, + "group-borderColor": { + "value": "#555555", + "edited": false + }, + "group-backgroundColor": { + "value": "#333333", + "edited": false + }, + "widget-textColor": { + "value": "#eeeeee", + "edited": false + }, + "widget-backgroundColor": { + "value": "#097479", + "edited": false + }, + "widget-borderColor": { + "value": "#333333", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "OpenScan", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "34bc0fd2b0f2416c", + "type": "ui_link", + "name": "GitHub", + "link": "https://openscan-org.github.io/OpenScan-Doc/", + "icon": "fa-bookmark", + "target": "iframe", + "order": 6 + }, + { + "id": "23f75a8768250ce8", + "type": "ui_link", + "name": "Patreon", + "link": "https://www.patreon.com/OpenScan", + "icon": "fa-bookmark", + "target": "newtab", + "order": 5 + }, + { + "id": "b5fdd57b.15eda8", + "type": "ui_group", + "name": "Main", + "tab": "15a222ed.d70a7d", + "order": 1, + "disp": false, + "width": 13, + "collapse": false + }, + { + "id": "db43d646.2074c8", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "15a222ed.d70a7d", + "order": 2, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "15a222ed.d70a7d", + "type": "ui_tab", + "name": "Files&Cloud", + "icon": "dashboard", + "order": 3, + "disabled": false, + "hidden": false + }, + { + "id": "365a30d0dfa83e95", + "type": "ui_group", + "name": "settings", + "tab": "e23b837a9f040895", + "order": 1, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "ac7409105cfecac6", + "type": "ui_group", + "name": "advanced", + "tab": "e23b837a9f040895", + "order": 3, + "disp": false, + "width": 7, + "collapse": false, + "className": "" + }, + { + "id": "729f9ea6e3513c9b", + "type": "ui_group", + "name": "Home", + "tab": "b3150b13e34b1fe8", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "5b3e5aca21140e9a", + "type": "ui_group", + "name": "Update", + "tab": "b3150b13e34b1fe8", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "b3150b13e34b1fe8", + "type": "ui_tab", + "name": "OpenScan", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": true + }, + { + "id": "ddbd496e.93a288", + "type": "ui_group", + "name": "Manage Updates", + "tab": "d25e08b4.5b27e8", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "3ce32450.e0cffc", + "type": "ui_group", + "name": "System & Stats", + "tab": "d25e08b4.5b27e8", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "d25e08b4.5b27e8", + "type": "ui_tab", + "name": "Update & Info", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "4390b2ebcbbe104c", + "type": "ui_group", + "name": "General", + "tab": "457102eadc9ddb6c", + "order": 1, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "8ab79a98e536e0d6", + "type": "ui_group", + "name": "Network", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "70d0be671bf03ca7", + "type": "ui_group", + "name": "Pinout", + "tab": "457102eadc9ddb6c", + "order": 6, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "7a3279eea439bcdd", + "type": "ui_group", + "name": "Motor", + "tab": "457102eadc9ddb6c", + "order": 5, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "d324f0b852c2df0a", + "type": "ui_group", + "name": "Camera", + "tab": "457102eadc9ddb6c", + "order": 4, + "disp": true, + "width": "6", + "collapse": true, + "className": "" + }, + { + "id": "12b719cba49817c9", + "type": "ui_group", + "name": "OpenScanCloud", + "tab": "457102eadc9ddb6c", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "457102eadc9ddb6c", + "type": "ui_tab", + "name": "Settings", + "icon": "dashboard", + "order": 4, + "disabled": false, + "hidden": false + }, + { + "id": "6e339d87c7d5debe", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 1, + "width": 1, + "height": 1 + }, + { + "id": "33b6d7317d1524b8", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "db43d646.2074c8", + "order": 3, + "width": 1, + "height": 1 + }, + { + "id": "aaf5b874c52a58aa", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 8, + "width": 7, + "height": 1 + }, + { + "id": "2e08d4415665c939", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 9, + "width": 1, + "height": 1 + }, + { + "id": "f8d8740dcbf499fb", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 11, + "width": 1, + "height": 1 + }, + { + "id": "7ac0cb556740d159", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "365a30d0dfa83e95", + "order": 13, + "width": 1, + "height": 1 + }, + { + "id": "4de2414e29020c74", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "90223f7ddc082321", + "order": 2, + "width": 7, + "height": 1 + }, + { + "id": "ac8c60543cb04139", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "ac7409105cfecac6", + "order": 3, + "width": 7, + "height": 1 + }, + { + "id": "ce21673092264c38", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "8ab79a98e536e0d6", + "order": 3, + "width": 6, + "height": 1 + }, + { + "id": "3f7b77f8a1675d27", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "12b719cba49817c9", + "order": 7, + "width": 4, + "height": 1 + }, + { + "id": "0799b02d12fc3a14", + "type": "ui_spacer", + "z": "e43a27722b508115", + "name": "spacer", + "group": "7a3279eea439bcdd", + "order": 25, + "width": 6, + "height": 1 + }, + { + "id": "220493325bb79987", + "type": "ui_group", + "name": "Messaging", + "tab": "457102eadc9ddb6c", + "order": 7, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "bc4e2c03859196c3", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 460, + "wires": [ + [ + "949bafced17d66d6" + ] + ] + }, + { + "id": "949bafced17d66d6", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.flag = global.set('flag_pw',true)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "a1f0ed7d5a9d670e", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "0.1", + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "544d20f02215011a", + "325314c1a24fe5b4", + "7a4a49f7dbe04e88", + "b1e2491c952f84c9", + "fac6626127bba4f5", + "bc2f0adaf72f97e9", + "ac242724fe7605a6" + ] + ] + }, + { + "id": "544d20f02215011a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "CREATE FACTORY DEFAULT", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 330, + "y": 60, + "wires": [ + [ + "c77552216a8bb781" + ] + ] + }, + { + "id": "c77552216a8bb781", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "chk files", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 540, + "y": 60, + "wires": [ + [ + "960912e90ba5b5bc" + ] + ] + }, + { + "id": "960912e90ba5b5bc", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "1dffb799fdf10cbc", + "9fd259de91de1da1", + "fd0258418489839d", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 645, + "y": 60, + "wires": [] + }, + { + "id": "325314c1a24fe5b4", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "create path", + "func": "import os\n\npaths = ['/home/pi/OpenScan/scans/preview/','/home/pi/OpenScan/tmp2/']\n\n\nfor i in paths:\n if not os.path.isdir(i):\n os.mkdir(i)", + "outputs": 1, + "x": 270, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "168d72a54504b327", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "5/0.1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "5", + "topic": "", + "payload": "", + "payloadType": "str", + "x": 100, + "y": 380, + "wires": [ + [ + "6c6ef2255a7d39e5" + ] + ] + }, + { + "id": "6c6ef2255a7d39e5", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "repeat 5s/0.1s", + "mode": "link", + "links": [ + "61990987acd0f263", + "2415272f42ce468c", + "6bf8344af427a6ba" + ], + "x": 205, + "y": 380, + "wires": [] + }, + { + "id": "7a4a49f7dbe04e88", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "LED Status", + "func": "from OpenScan import fade_led, check_hotspot_mode, load_int\n\npin = load_int(\"pin_ringlight1\")\npin2 = load_int(\"pin_ringlight2\")\n\nif check_hotspot_mode():\n msg['mode'] = True\n i=4\n j=30\nelse:\n msg['mode'] = False\n i=2\n j=30\n\nfor x in range (i):\n fade_led(pin,j, 50, True)\n #fade_led(pin2,j, 50, True)\n fade_led(pin,j, 50, False)\n #fade_led(pin2,j, 50, False)\n pass\nreturn msg", + "outputs": 1, + "x": 270, + "y": 140, + "wires": [ + [ + "eb1a2387a1eeea76" + ] + ] + }, + { + "id": "b1e2491c952f84c9", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "global", + "func": "global.set('light', 0)\nglobal.set('state1', 0)\nglobal.set('network_ssid',\"\")\nglobal.set('network_password',\"\")\nglobal.set('network_country',\"\")\nglobal.set('flag_pw', true)\nglobal.set('flag',false)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "fac6626127bba4f5", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "enable", + "func": "msg.enabled = true\nmsg.payload = \"\"\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 280, + "wires": [ + [ + "200d4b9951b6e066" + ] + ] + }, + { + "id": "200d4b9951b6e066", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "c8b93b42c720b9cf", + "65518f3d4e3095e5" + ], + "x": 345, + "y": 280, + "wires": [] + }, + { + "id": "bc2f0adaf72f97e9", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "CAM init", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_init\")\n\nmotor_enable_pin = 22\n\nimport RPi.GPIO as GPIO # import RPi.GPIO module\nGPIO.setmode(GPIO.BCM) # choose BCM or BOARD\nGPIO.setwarnings(False)\nGPIO.setup(22, GPIO.OUT) # set a port/pin as an output\nGPIO.output(22, 0) \n", + "outputs": 1, + "x": 260, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "8def60b68e21e665", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "FACTORY DEFAULT", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 800, + "y": 40, + "wires": [ + [ + "544d20f02215011a" + ] + ] + }, + { + "id": "eb1a2387a1eeea76", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "enable LED", + "mode": "link", + "links": [ + "592ec13d8f8923a9", + "5baf89a2682265f7" + ], + "x": 385, + "y": 140, + "wires": [] + }, + { + "id": "0d8c6bc7887fb3c2", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "365a30d0dfa83e95", + "name": "shutdown+background", + "order": 14, + "width": 7, + "height": 1, + "format": "\n\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "global", + "className": "", + "x": 580, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "ac242724fe7605a6", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "rescue incomplete project", + "func": "#if project has not been done properly, this is a way to rescue the file\n\nfrom os import system\nfrom os.path import isfile\nfrom time import strftime\nfrom OpenScan import load_str\n\nbasepath = '/home/pi/OpenScan/'\nzippath = basepath + 'tmp/tmp.zip'\nprojectname=load_str(\"routine_projectname\")\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip')", + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "4468f691.103eb8", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 1, + "width": 3, + "height": 2, + "passthru": false, + "label": "SCAN", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "1", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 540, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "6560dd25.9e76c4", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 3, + "width": 3, + "height": 2, + "passthru": false, + "label": "Settings", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "3", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 100, + "y": 620, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "62cd5288.2805fc", + "type": "ui_ui_control", + "z": "e6f4d02efb300ea9", + "name": "", + "events": "all", + "x": 280, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "71e72293.91c6fc", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 2, + "width": 3, + "height": 2, + "passthru": false, + "label": "Files", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "2", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 90, + "y": 580, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "e7306ef2.3b4df", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 4, + "width": 3, + "height": 2, + "passthru": false, + "label": "Update&Info", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "4", + "payloadType": "num", + "topic": "", + "topicType": "str", + "x": 110, + "y": 660, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "8955d11554f55e63", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "5b3e5aca21140e9a", + "order": 1, + "width": 6, + "height": 3, + "passthru": false, + "label": "Install Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "date", + "topic": "", + "topicType": "str", + "x": 120, + "y": 720, + "wires": [ + [ + "1e7457ea9c2c5e09" + ] + ] + }, + { + "id": "1e7457ea9c2c5e09", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "update", + "mode": "link", + "links": [ + "39a502b38837273d" + ], + "x": 245, + "y": 720, + "wires": [] + }, + { + "id": "245e4341d4fb611c", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "pinmap_v2", + "func": "msg = { \n'overwrite':true,\n'settings':{\n 'pin_rotor_endstop':27,\n 'pin_tt_endstop':5,\n 'pin_extra_endstop':26,\n 'pin_external':25,\n 'pin_ringlight1':24,\n 'pin_ringlight2':24,\n 'pin_rotor_dir':23,\n 'pin_rotor_enable':19,\n 'pin_rotor_step':22,\n 'pin_tt_dir':6,\n 'pin_tt_enable':19,\n 'pin_tt_step':16,\n 'pin_extra_dir':21,\n 'pin_extra_step':20,\n 'pin_extra_enable':19,\n 'extra_acc':1,\n 'extra_accramp':200,\n 'extra_angle':10,\n 'extra_delay':0.0001,\n 'extra_dir':1,\n 'extra_stepsperrotation':3200,\n}}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 540, + "wires": [ + [ + "627406f3611511dc" + ] + ] + }, + { + "id": "627406f3611511dc", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "write", + "func": "from os import listdir\n\nbasedir = '/home/pi/OpenScan/settings/'\n\nmsg['payload'] = ''\n\nfiles = listdir(basedir)\n\nfor i in msg['settings']:\n if msg['overwrite'] != True:\n if i not in files:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n else:\n with open(basedir + i, 'w+') as file:\n file.write(str(msg['settings'][i])) \n\nmsg['payload'] = True\n\nreturn msg", + "outputs": 1, + "x": 930, + "y": 540, + "wires": [ + [ + "50eeb3e362f9027f" + ] + ] + }, + { + "id": "88b1bddde110298a", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0.1", + "topic": "", + "x": 650, + "y": 540, + "wires": [ + [ + "245e4341d4fb611c" + ] + ] + }, + { + "id": "50eeb3e362f9027f", + "type": "link out", + "z": "e6f4d02efb300ea9", + "name": "started1s", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "397ab7f44b893c89", + "65145c939b6647e2", + "65b38bfeb3fee710", + "6d1e12f51f9af0b6", + "788fabff98c7973c", + "9b2bc9849aee310b", + "a1e14624058e74cd", + "a67c18aaca2f5fa5", + "bd80ec228fb9a86d", + "cc9c4092edeb43cc", + "d3fc91d87d5d5f62", + "d7c1fb4c028b21a5", + "e5f38b4a07a5e278", + "f0b355967b33dfee", + "d0104e0163745993", + "5e7d5e4335d37794", + "b4c843620c251c43", + "3876d5cbd248592b", + "a4c81754c148b86f", + "2e9b29c70969cf01", + "2477f81cddc8fa31", + "29036b35dfd672c6", + "592ec13d8f8923a9", + "cb40b9341bd22a28", + "d1efcd5fa9d25785", + "da61581182b7299e", + "2afb6a45c73fa244" + ], + "x": 1015, + "y": 540, + "wires": [] + }, + { + "id": "4f3121f158f06a61", + "type": "python3-function", + "z": "e6f4d02efb300ea9", + "name": "motor run", + "func": "from OpenScan import motorrun, load_int\nfrom time import sleep\n\nmotorrun('rotor',300,True,False)\n\n", + "outputs": 1, + "x": 860, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "4a8a04b1e5dca8fe", + "type": "inject", + "z": "e6f4d02efb300ea9", + "name": "run rotor till endstop", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 690, + "y": 580, + "wires": [ + [ + "4f3121f158f06a61" + ] + ] + }, + { + "id": "c8167775e3401fad", + "type": "ui_template", + "z": "e6f4d02efb300ea9", + "group": "729f9ea6e3513c9b", + "name": "infotext", + "order": 4, + "width": 0, + "height": 0, + "format": "

What's new?

\n
    \n
  • speed improvement 2-3x
  • \n
  • currently tested on OpenScan Mini + IMX519 with RPi 4
  • \n
  • optimized toolpath
  • \n
  • more responsive user interface
  • \n
  • hotspot mode (when no wireless network available ssid: openscan pw: opensource
  • \n
  • preview features and sharpness
  • \n
  • partial background masking
  • \n
  • no more autofocus --> instead you can set a min and max focus distance
  • \n
\nnote, that this is still an early beta and there might be some unintended bugs. please reach out to info@openscan.eu if you run into any issues.", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 580, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "6a3d9acbe097a3d2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 120, + "wires": [ + [ + "cb6ebdabaaf7d0da" + ] + ] + }, + { + "id": "7ef6f1b5c67201fe", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_photocount'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "86f7d1b2d763f6e2", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 160, + "wires": [ + [ + "c8a3fde5206ce1ae" + ] + ] + }, + { + "id": "fd799c931139764d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 240, + "wires": [ + [ + "87be854db758a9a6" + ] + ] + }, + { + "id": "d5140d455122c49a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 280, + "wires": [ + [ + "9daea4bd57f7a00e" + ] + ] + }, + { + "id": "194f3590dd4f6e3d", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropx'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "2de69452e829d780", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_cropy'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "58e565fea35cb667", + "type": "ui_text_input", + "z": "481edaf6db5a7a54", + "name": "", + "label": "", + "tooltip": "", + "group": "365a30d0dfa83e95", + "order": 3, + "width": 4, + "height": 1, + "passthru": true, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 320, + "y": 80, + "wires": [ + [ + "734ac3bff2df6837" + ] + ] + }, + { + "id": "97170908e1f4ac55", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.payload=\"default\"\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 80, + "wires": [ + [ + "58e565fea35cb667" + ] + ] + }, + { + "id": "734ac3bff2df6837", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\n\nvar file = 'routine_projectname'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload).replace(/ /g, '_')\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "1dffb799fdf10cbc", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 55, + "y": 80, + "wires": [ + [ + "97170908e1f4ac55", + "6a3d9acbe097a3d2", + "86f7d1b2d763f6e2", + "fd799c931139764d", + "d5140d455122c49a" + ] + ] + }, + { + "id": "a0156eaac7dd35e5", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "shutter", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\nimport math\n\n\ncamera('/v1/camera/picam2_exposure?exposure=' + str(int(msg['payload']*1000)))\n\nreturn msg\n", + "outputs": 1, + "x": 510, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "c7f5808d753480d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "6", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 170, + "y": 200, + "wires": [ + [ + "11f41a6030578ef4" + ] + ] + }, + { + "id": "11f41a6030578ef4", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) / 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 310, + "y": 200, + "wires": [ + [ + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "855cbcadef1163c5", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "msg.light = global.get('light')\nmsg.state1 = global.get('state1')\nmsg.flag = global.get('flag')\n\n\nvar min = 1;\nvar max = 100000;\nvar random = Math.floor(Math.random() * (max - min + 1)) + min;\n\nvar formatted = random.toString().padStart(3, '0');\nmsg.payload=\"/tmp2/preview.jpg?ts=\" + Date.now().toString();\n\nif (global.get('flag_pw') == false){\n if (msg.flag == true){\n return msg\n }\n return \n}\nelse{\n return msg\n}\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 840, + "wires": [ + [ + "d1b87196ae5373ed", + "41e6a4649b6afbfb", + "2fd24f8e8e9c08b7", + "85a268108250ba88" + ] + ] + }, + { + "id": "1a443e20a973d2f1", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw true", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 630, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "d1b87196ae5373ed", + "type": "change", + "z": "481edaf6db5a7a54", + "name": "flag_pw false", + "rules": [ + { + "t": "set", + "p": "flag_pw", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "03d92601c62b79d4", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "4s/0.5", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "0.1", + "crontab": "", + "once": true, + "onceDelay": "4", + "topic": "Repeat", + "payload": "0.1", + "payloadType": "str", + "x": 100, + "y": 840, + "wires": [ + [ + "855cbcadef1163c5" + ] + ] + }, + { + "id": "41e6a4649b6afbfb", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Take Preview Shot", + "func": "from time import time, sleep\nfrom OpenScan import load_str, load_bool, take_photo, camera, load_int,save\n\nstatus = load_str('status_internal_cam')\n\n#return msg\n\nmsg['payload']=\"/tmp2/preview.jpg?ts=\"+str(int(time()))\n\nif msg['flag'] == True:\n return msg\n\n\n#if status!=\"--READY--\":\n# return msg\n\n#msg['preview'] = True\n\ncamera('/v1/camera/picam2_take_photo')\n\nreturn msg\n", + "outputs": 1, + "x": 450, + "y": 800, + "wires": [ + [ + "1a443e20a973d2f1", + "296636b7467fc745" + ] + ] + }, + { + "id": "85a268108250ba88", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "preview_arducam", + "order": 1, + "width": 7, + "height": 9, + "format": "\n\n
\n \n
\n \n
\n
\n \n \n \n
\n\n \n\n\n\n \n \n
\n \n \n \n \n \n \n
\n \n
\n \n\n\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 450, + "y": 840, + "wires": [ + [ + "417f653ca0dfdcfc", + "180476141c2a44ad" + ] + ] + }, + { + "id": "296636b7467fc745", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "link out 1", + "mode": "link", + "links": [ + "2c58a1a66c4a8c11" + ], + "x": 575, + "y": 800, + "wires": [] + }, + { + "id": "417f653ca0dfdcfc", + "type": "delay", + "z": "481edaf6db5a7a54", + "name": "lmt 0.2/s", + "pauseType": "rate", + "timeout": "0.1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "0.2", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 640, + "y": 840, + "wires": [ + [ + "e864254b18c23dd1" + ] + ] + }, + { + "id": "e864254b18c23dd1", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "motorrun", + "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "outputs": 1, + "x": 780, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "180476141c2a44ad", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "global", + "func": "if (typeof msg.light !== \"undefined\"){\n global.set('light',msg.light)\n}\nif (typeof msg.state1 !== \"undefined\"){\n global.set('state1',msg.state1)\n}\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 880, + "wires": [ + [ + "8cbdbfecbd12ef83" + ] + ] + }, + { + "id": "1fe18f3b0b52aabd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "LED", + "func": "from OpenScan import ringlight\nfrom time import time\n\nstarttime = time()\n\nif 'light' in msg:\n val = msg['light']\n while time()-starttime<0.02:\n if val == 0:\n ringlight(1,False)\n ringlight(2,False)\n\n elif val == 1:\n ringlight(1,True)\n ringlight(2,True)\n\nreturn msg", + "outputs": 1, + "x": 870, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "2fd24f8e8e9c08b7", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif 'state1' in msg:\n if msg['state1'] == 0:\n msg['payload']={\"group\":{\"hide\":[\"Scan_advanced\"],\"show\":[]}}\n else:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Scan_advanced\"]}}\n return msg", + "outputs": 1, + "x": 440, + "y": 720, + "wires": [ + [ + "923be3b2b25224b4" + ] + ] + }, + { + "id": "923be3b2b25224b4", + "type": "ui_ui_control", + "z": "481edaf6db5a7a54", + "name": "change visibility", + "events": "all", + "x": 640, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "c8a3fde5206ce1ae", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "shutter", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 160, + "wires": [ + [ + "034ec9f59e50a361", + "a0156eaac7dd35e5" + ] + ] + }, + { + "id": "034ec9f59e50a361", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_shutter'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload * 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "87be854db758a9a6", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropy", + "order": 7, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 240, + "wires": [ + [ + "194f3590dd4f6e3d" + ] + ] + }, + { + "id": "9daea4bd57f7a00e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Cropx", + "order": 6, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 280, + "wires": [ + [ + "2de69452e829d780" + ] + ] + }, + { + "id": "cb6ebdabaaf7d0da", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "name": "Photos", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 120, + "wires": [ + [ + "7ef6f1b5c67201fe" + ] + ] + }, + { + "id": "82ecd3cd971cb7ea", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 2, + "width": 3, + "height": 1, + "name": "projectname", + "label": "Projectname", + "format": "", + "layout": "row-left", + "className": "", + "x": 530, + "y": 40, + "wires": [] + }, + { + "id": "ed2974731fb8a84e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "threshold", + "order": 5, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 520, + "wires": [ + [ + "06e1e19835a9816e" + ] + ] + }, + { + "id": "8cbdbfecbd12ef83", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "led", + "func": "from OpenScan import fade_led, ringlight, load_int\n\npin = load_int('pin_ringlight1')\n\n\nif 'light' in msg:\n val = msg['light']\n\n if val ==1:\n fade_led(pin,50, 100, True)\n\n else:\n fade_led(pin,50, 100, False)\n\nreturn msg", + "outputs": 1, + "x": 750, + "y": 880, + "wires": [ + [ + "1fe18f3b0b52aabd" + ] + ] + }, + { + "id": "06e1e19835a9816e", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2d5b1eb4380ae5a8", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_mask_threshold'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 520, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "7dd287f40385922f", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "start ", + "group": "365a30d0dfa83e95", + "order": 10, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-play", + "payload": "", + "payloadType": "date", + "topic": "enabled", + "topicType": "str", + "x": 130, + "y": 1040, + "wires": [ + [ + "33d94a04b96a2de0", + "6d15f717d5a11002", + "9a6b30a0175a8ecd" + ] + ] + }, + { + "id": "579f2211199fd6ab", + "type": "ui_button", + "z": "481edaf6db5a7a54", + "name": "stop", + "group": "365a30d0dfa83e95", + "order": 12, + "width": 2, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "fa-stop", + "payload": "numberofphotos", + "payloadType": "global", + "topic": "", + "topicType": "str", + "x": 490, + "y": 1100, + "wires": [ + [ + "1787f08ed7070ddd", + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "1787f08ed7070ddd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "stop", + "func": "from OpenScan import load_str, save\n\nstatus = load_str('status_internal_cam')\n\nif status == 'no camera found' or status[:5]=='Featu' or status =='--READY--':\n return\n\nsave('status_internal_cam', 'Routine-stopping')", + "outputs": 1, + "x": 630, + "y": 1100, + "wires": [ + [] + ] + }, + { + "id": "e9b13dfd9f8d3711", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 395, + "y": 1000, + "wires": [] + }, + { + "id": "9654deebb668e012", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "1s", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": "1", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 290, + "y": 1140, + "wires": [ + [ + "c1c044f3c2139f68" + ] + ] + }, + { + "id": "8367cfa0bf5bc5df", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine", + "links": [ + "200d4b9951b6e066", + "8689e938.dd9e38", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 45, + "y": 1040, + "wires": [ + [ + "7dd287f40385922f" + ] + ] + }, + { + "id": "fb13752beddee9f2", + "type": "link out", + "z": "481edaf6db5a7a54", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2", + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "c8b93b42c720b9cf" + ], + "x": 535, + "y": 1060, + "wires": [] + }, + { + "id": "33d94a04b96a2de0", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('flag', false)\n\nvar file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\n\n\nif (data === 'no camera found' || data.substring(0,5) === 'Featu'){\n return\n}\n\nmsg.enabled = true\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1100, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "c1c044f3c2139f68", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "msg.enabled = false\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 490, + "y": 1140, + "wires": [ + [ + "579f2211199fd6ab" + ] + ] + }, + { + "id": "1daf9e3a5bd5ab48", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "msg", + "func": "global.set('flag_pw', true)\nglobal.set('flag', false)\nmsg.enabled = true\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 430, + "y": 1040, + "wires": [ + [ + "fb13752beddee9f2" + ] + ] + }, + { + "id": "6d15f717d5a11002", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "disable", + "func": "msg.enabled = false\nmsg.payload = false\nglobal.set(\"flag\",true)\n\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 1000, + "wires": [ + [ + "e9b13dfd9f8d3711" + ] + ] + }, + { + "id": "9a6b30a0175a8ecd", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "Routine", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n \n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\nimport os\n\ntry:\n os.remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "outputs": 1, + "x": 300, + "y": 1040, + "wires": [ + [ + "1daf9e3a5bd5ab48", + "795c85ad4f109567" + ] + ] + }, + { + "id": "afe47a9eaae6f67f", + "type": "ui_text", + "z": "481edaf6db5a7a54", + "group": "365a30d0dfa83e95", + "order": 1, + "width": 7, + "height": 1, + "name": "", + "label": "Current Status:", + "format": " {{msg.payload}} ", + "layout": "row-spread", + "className": "", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "8608517d0567d63f", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadS", + "func": "var file = 'status_internal_cam'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data);\n\nif (data === 'no camera found'){\n msg.color = 'red'\n}\n\nreturn msg\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 40, + "wires": [ + [ + "afe47a9eaae6f67f" + ] + ] + }, + { + "id": "6bf8344af427a6ba", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start status", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 55, + "y": 40, + "wires": [ + [ + "8608517d0567d63f" + ] + ] + }, + { + "id": "78cfe60013a1bea4", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Sharpness", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 2, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 350, + "y": 380, + "wires": [ + [ + "9774e7ad3b506354" + ] + ] + }, + { + "id": "9774e7ad3b506354", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_sharparea',msg['payload'])\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 380, + "wires": [ + [ + "f0af909f3e739b22" + ] + ] + }, + { + "id": "39c744466a21735e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_min", + "order": 3, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 40, + "wires": [ + [ + "fa181d22775c2ce6" + ] + ] + }, + { + "id": "61aab497fa50898e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "90223f7ddc082321", + "name": "focus_max", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 80, + "wires": [ + [ + "c615034ea6b26174" + ] + ] + }, + { + "id": "5e83b653850fa16e", + "type": "ui_template", + "z": "481edaf6db5a7a54", + "group": "ac7409105cfecac6", + "name": "stacksize", + "order": 4, + "width": 7, + "height": 1, + "format": "\n \n \n
\n
\n
\n {{sliderName}}\n
\n
\n \n \n \n
\n
\n
\n \n \n
\n
\n\n\n
", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 320, + "y": 480, + "wires": [ + [ + "237c2135cdad86ea" + ] + ] + }, + { + "id": "dd7fb8791d34c751", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "enable", + "func": "global.set('light', 1)\nmsg.light = 1\nreturn msg\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 880, + "wires": [ + [ + "180476141c2a44ad" + ] + ] + }, + { + "id": "5baf89a2682265f7", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "enable led", + "links": [ + "eb1a2387a1eeea76" + ], + "x": 145, + "y": 880, + "wires": [ + [ + "dd7fb8791d34c751" + ] + ] + }, + { + "id": "6a26e8a7253d708c", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 40, + "wires": [ + [ + "39c744466a21735e" + ] + ] + }, + { + "id": "35ad7e55833836c1", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadF", + "func": "var file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 830, + "y": 80, + "wires": [ + [ + "61aab497fa50898e" + ] + ] + }, + { + "id": "9fd259de91de1da1", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 735, + "y": 40, + "wires": [ + [ + "6a26e8a7253d708c", + "35ad7e55833836c1" + ] + ] + }, + { + "id": "fa181d22775c2ce6", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_min'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 40, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "c615034ea6b26174", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "rate", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_focus_max'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n return msg\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 80, + "wires": [ + [ + "ae5ee8787145906d" + ] + ] + }, + { + "id": "ae5ee8787145906d", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import camera\ncamera('/v1/camera/picam2_focus?focus=' + str(msg['payload']))\n\nreturn msg", + "outputs": 1, + "x": 1290, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f0af909f3e739b22", + "type": "ui_switch", + "z": "481edaf6db5a7a54", + "name": "", + "label": "Show Features", + "tooltip": "", + "group": "ac7409105cfecac6", + "order": 1, + "width": 7, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 420, + "wires": [ + [ + "710fc2dbb5ef0167" + ] + ] + }, + { + "id": "710fc2dbb5ef0167", + "type": "python3-function", + "z": "481edaf6db5a7a54", + "name": "focus", + "func": "from OpenScan import save\nsave('cam_features',msg['payload'])\n\n\nif not 'flag' in msg:\n msg['flag'] = True\n msg['payload'] = False\n return msg", + "outputs": 1, + "x": 510, + "y": 420, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "d93c2b67bcf23b9a", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "loadI", + "func": "var file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 480, + "wires": [ + [ + "5e83b653850fa16e" + ] + ] + }, + { + "id": "237c2135cdad86ea", + "type": "function", + "z": "481edaf6db5a7a54", + "name": "write", + "func": "const delay = 100; // 100 ms delay\n\nif (!context.timeout) {\n context.timeout = setTimeout(() => {\n node.send({ payload: msg.payload });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n} else {\n context.set(\"lastMessage\", msg.payload);\n clearTimeout(context.timeout);\n context.timeout = setTimeout(() => {\n node.send({ payload: context.get(\"lastMessage\") });\n clearTimeout(context.timeout);\n delete context.timeout;\n }, delay);\n}\nvar file = 'cam_stacksize'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "fd0258418489839d", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "start routine settings", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 480, + "wires": [ + [ + "2d5b1eb4380ae5a8", + "d93c2b67bcf23b9a" + ] + ] + }, + { + "id": "c6f281351e11b58a", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 600, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "ca4ca7fae36d312d", + "type": "inject", + "z": "481edaf6db5a7a54", + "name": "", + "props": [ + { + "p": "enabled", + "v": "false", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ed2974731fb8a84e" + ] + ] + }, + { + "id": "c8b93b42c720b9cf", + "type": "link in", + "z": "481edaf6db5a7a54", + "name": "sharpness/features", + "links": [ + "200d4b9951b6e066", + "e9b13dfd9f8d3711", + "fb13752beddee9f2" + ], + "x": 85, + "y": 380, + "wires": [ + [ + "78cfe60013a1bea4" + ] + ] + }, + { + "id": "795c85ad4f109567", + "type": "debug", + "z": "481edaf6db5a7a54", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 620, + "y": 1000, + "wires": [] + }, + { + "id": "ea54fcc2.cfcc2", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "get dirs", + "func": "from glob import glob\nimport os\nfrom zipfile import ZipFile\nfrom datetime import datetime\nfrom PIL import Image\n\ndef set_stats(stat):\n try:\n with open(directory+set[:-4]+\"/\"+stat,\"r\") as file:\n stat=file.read()\n except:\n stat=\"\"\n return stat\n\ntable=[]\ndirectory=\"/home/pi/OpenScan/scans/\"\n\nfor d in glob(directory+\"*.zip\"):\n set=os.path.basename(d)\n\n try:\n with ZipFile(d, 'r') as f:\n photos = len(f.namelist())\n \n if not os.path.isfile(directory + 'preview/' + os.path.basename(d)[:-4]+'.jpg'):\n image = f.open(f.namelist()[int(photos/2)])\n img = Image.open(image)\n width, height = img.size\n width_factor = width/300\n height_factor = height/295\n if height_factor>=width_factor and height_factor > 1:\n new_size=(int(width/height_factor), int(height/height_factor))\n img = img.resize(new_size)\n elif height_factor 1:\n new_size=(int(width/width_factor),int(height/width_factor))\n img = img.resize(new_size)\n img.save(directory + 'preview/' + os.path.basename(d)[:-4] +'.jpg')\n list=[]\n for fi in f.filelist:\n list.append(f.getinfo(fi.filename).date_time)\n \n duration = str(datetime(*max(list)) - datetime(*min(list)))\n \n size = float(int(float(os.path.getsize(d))/100000))/10\n size_full= os.path.getsize(d)\n status=set_stats(\"status\")\n expiration=set_stats(\"expiration\")\n download=set_stats(\"download\")\n \n if len(download)!=0:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Download\":\"RESULT\",\n \"Size_full\":size_full,\n \"Duration\":duration,\n })\n else:\n table.append({\n \"Set\":set,\n \"Photos\":photos,\n \"Size\":str(size)+\"MB\",\n \"Date\":set[:16],\n \"Name\":(set[20:-4]),\n \"Status\":status,\n \"Size_full\":size_full,\n \"Duration\":duration,\n\n })\n except:\n pass\n\nmsg['payload']=table\nmsg['topic']=\"\"\nreturn msg", + "outputs": 1, + "x": 480, + "y": 180, + "wires": [ + [ + "f3662f8c7d3d7a2d", + "01e4783e148c6698" + ] + ] + }, + { + "id": "2f4c0f98.dee2", + "type": "link in", + "z": "80a3942785a26c29", + "name": "filelist", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "a4f09e25.02569", + "ed35109311335099", + "fb13752beddee9f2" + ], + "x": 355, + "y": 220, + "wires": [ + [ + "ea54fcc2.cfcc2" + ] + ] + }, + { + "id": "952ce286.4ffd4", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 4, + "width": 6, + "height": 1, + "name": "Status", + "label": "Status", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 250, + "y": 60, + "wires": [] + }, + { + "id": "d4383424.7807c8", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "upload", + "func": "import os\nfrom OpenScan import OpenScanCloud, load_str, load_int, save\nfrom subprocess import getoutput\n\nbasedir = '/home/pi/OpenScan/'\n\nif load_str(\"feedback_terms\")==\"False\":\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic'] = 'OpenScanCloud - Terms of Use'\n return None,msg\n\nmsg = msg['payload']\n\ndef upload(filelist, ulinks):\n pid = getoutput('pidof curl')\n if pid != \"\":\n os.system('kill ' + pid)\n\n i = 0\n for file in filelist:\n link = ulinks[i]\n save('status_cloud', 'uploading ' + str(i+1) + '/' + str(len(filelist)))\n cmd = 'curl -# -X POST ' + link + ' --header Content-Type:application/octet-stream --data-binary @\"' + file + '\" 2>&1 | tee /home/pi/OpenScan/settings/status_uploadprogress'\n i = i+1\n os.system(cmd)\n\n########\nif not os.path.isfile(basedir + 'settings/token'):\n msg['flag'] = True\n save('status_cloud', 'please enter token first')\n return msg\nwith open(basedir + 'settings/token', 'r') as file:\n token = file.read().strip('\\n')\n\n########\nr = OpenScanCloud('getTokenInfo', {'token':token})\n\nif r.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n save('status_cloud', 'invalid/missing token')\n return None,msg\nelif r.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nmsg1 = r.json()\n\n########\nif msg['Photos'] > msg1['limit_photos'] or msg['Size_full'] > msg1['limit_filesize']:\n msg['flag'] = True\n save('status_cloud', 'limit(s) exceeded')\n return msg\n\n########\ntemp = OpenScanCloud('getProjectInfo', {'token':token, 'project':msg['Set']})\nif temp.status_code not in (200,401):\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nif temp.status_code != 401:\n temp = temp.json()\n if 'status' in temp:\n if temp['status'] != 'created':\n save('status_cloud','already exists')\n with open(basedir + 'scans/' + msg['Set'][:-4] + '/status', 'w') as file:\n file.write(temp['status'])\n return msg\n#####\n\nmsg2={}\nmsg2['token'] = token\nmsg2['parts'] = 1\nmsg['partslist']=[]\n\n#######\nsize_to_split = load_int('osc_splitsize')\n\nif msg['Size_full'] > size_to_split:\n tempdir = basedir + 'tmp/split/'\n if os.path.isdir(tempdir):\n os.system('rm -r ' + tempdir)\n os.mkdir(tempdir)\n save('status_cloud', 'zipping files, please wait ...')\n cmd = 'split -b ' + str(size_to_split) + ' ' + basedir + 'scans/' + msg['Set'] + ' ' + tempdir + msg['Set']\n os.system(cmd)\n save('status_cloud', 'zip done')\n list = os.listdir(tempdir)\n for l in list:\n msg['partslist'].append(tempdir + l)\n msg['partslist'].sort()\n msg2['parts']=len(msg['partslist'])\nelse:\n msg['partslist'] = [basedir + 'scans/' +msg['Set']]\n\n#######\nmsg2['photos'] = msg['Photos']\nmsg2['filesize'] = msg['Size_full']\nmsg2['project'] = msg['Set']\n\nr = OpenScanCloud('createProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Connection to OpenScanCloud failed'\n msg['payload'] = 'Please check your internet connection'\n return None,msg\n\nmsg1 = r.json()\n\nif not os.path.isdir(basedir+ 'scans/' + msg['Set'][:-4]):\n os.mkdir(basedir+ 'scans/' + msg['Set'][:-4])\nwith open(basedir+ 'scans/' + msg['Set'][:-4]+'/status', 'w+') as file:\n file.write('prepared')\n\nsave('status_cloud', 'uploading')\nupload(msg['partslist'], msg1['ulink'])\n\nr = OpenScanCloud('startProject', msg2)\nif r.status_code != 200:\n msg['topic'] = 'Upload failed'\n msg['payload'] = 'please try again'\n save('status_cloud', 'upload failed')\n return None,msg\n\nsave('status_cloud', 'uploaded')\n\nsave('status_cloud', 'project started')\n\ntry:\n os.system('rm -r ' + tempdir)\nexcept:\n pass\n\nreturn msg", + "outputs": 2, + "x": 530, + "y": 460, + "wires": [ + [ + "9a132ab1.b21658" + ], + [ + "3d16b3789632784d", + "9a132ab1.b21658" + ] + ] + }, + { + "id": "50710948.71c308", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "set", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 750, + "y": 180, + "wires": [ + [ + "ada1b6f7cccc9344", + "85839a17fb7b58b9" + ] + ] + }, + { + "id": "834046a4.647938", + "type": "ui_text", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "order": 5, + "width": 6, + "height": 1, + "name": "Set", + "label": "Set:", + "format": "{{msg.payload.Name}}", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 220, + "wires": [] + }, + { + "id": "9a132ab1.b21658", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.true", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "true", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 780, + "y": 460, + "wires": [ + [ + "8689e938.dd9e38" + ] + ] + }, + { + "id": "3c67e97b.9d19a6", + "type": "function", + "z": "80a3942785a26c29", + "name": "enable", + "func": "//if (global.get('flag') === false){\n// msg.enabled = false\n// msg.color=\"white\"\n//}\n//else{\n\n msg.enabled = true\n msg.color=\"red\"\n \n//}\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 130, + "y": 340, + "wires": [ + [ + "7a93d1e18254685c", + "e434ef42bd6b92e8", + "d5d840183025d91b", + "ab9e90ab5a53a0dd", + "478994f671a3907d" + ] + ] + }, + { + "id": "bfc01f26.c32cf", + "type": "change", + "z": "80a3942785a26c29", + "name": "flag.false", + "rules": [ + { + "t": "set", + "p": "flag", + "pt": "global", + "to": "false", + "tot": "bool" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 500, + "wires": [ + [ + "f20f2dbc.0f123" + ] + ] + }, + { + "id": "b33d604c.5f1a6", + "type": "link in", + "z": "80a3942785a26c29", + "name": "enable cloud", + "links": [ + "4082b136.dae18", + "8689e938.dd9e38", + "bd75f33b8a57c522", + "e9b13dfd9f8d3711", + "f20f2dbc.0f123", + "fb13752beddee9f2" + ], + "x": 35, + "y": 340, + "wires": [ + [ + "3c67e97b.9d19a6" + ] + ] + }, + { + "id": "f6bd1a04.470838", + "type": "change", + "z": "80a3942785a26c29", + "name": "set", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "set", + "tot": "global" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 410, + "y": 460, + "wires": [ + [ + "d4383424.7807c8" + ] + ] + }, + { + "id": "4082b136.dae18", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "b33d604c.5f1a6", + "87574a42938afec4" + ], + "x": 715, + "y": 140, + "wires": [] + }, + { + "id": "f20f2dbc.0f123", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 515, + "y": 500, + "wires": [] + }, + { + "id": "8689e938.dd9e38", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "8367cfa0bf5bc5df", + "b33d604c.5f1a6", + "149e2e46b9623a2d" + ], + "x": 875, + "y": 460, + "wires": [] + }, + { + "id": "15de0ebb.616d61", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 380, + "wires": [ + [ + "a7d89487.ee8858" + ] + ] + }, + { + "id": "a7d89487.ee8858", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\ntry:\n os.remove(dir+msg['Set'])\n shutil.rmtree(dir+msg['Set'][:-4])\nexcept:\n pass\nreturn msg", + "outputs": 1, + "x": 690, + "y": 380, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "a4f09e25.02569", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "2f4c0f98.dee2" + ], + "x": 775, + "y": 360, + "wires": [] + }, + { + "id": "7a93d1e18254685c", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "92c98e6ce7cd25f9" + ], + "x": 235, + "y": 500, + "wires": [] + }, + { + "id": "4d99c601c9881680", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "refresh", + "func": "from time import sleep\nimport os\nfrom OpenScan import load_str, OpenScanCloud, save, load_bool\n\nbasepath = '/home/pi/OpenScan/scans/'\n\nif load_bool(\"terms\")==False:\n msg['payload']=\"Please read and agree to the Terms of Use (See Settings Menu) before you can use the OpenScanCloud\"\n msg['topic']='OpenScanCloud - Terms of Use'\n return None,msg\n\nsave('status_cloud','refreshing')\ntoken = load_str('token')\n\ntest = OpenScanCloud('getTokenInfo',{'token':token})\nif test.status_code == 400:\n msg['topic'] = 'Invalid Token'\n msg['payload'] = 'Please enter a valid token (settings --> OpenScanCloud)'\n return None,msg\nelif test.status_code == 200:\n pass\nelse:\n msg['topic'] = 'Connection Error'\n msg['payload'] = 'Not able to establish a connection to OpenScanCloud.'\n return None,msg\n\nstats = test.json()\nfor i in stats:\n save('osc_'+i, stats[i])\n pass\n\nmsg={}\nprojects = []\nfor i in os.listdir(basepath):\n if i == 'preview':\n continue\n if os.path.isdir(basepath + i):\n if os.path.isfile(basepath + i + '/status'):\n with open(basepath + i + '/status', 'r') as file:\n status = file.read().strip('\\n')\n if status in ['expired', 'processing done', 'processing failed']:\n continue\n projects.append(i)\n\nfor p in projects:\n r = OpenScanCloud('getProjectInfo',{'token':token, 'project':p+'.zip'})\n if r.status_code == 200:\n answer = r.json()\n if answer == {}:\n os.system('rm -r ' + basepath + p)\n else:\n with open(basepath + p + '/status', 'w+') as file:\n file.write(answer['status'])\n with open(basepath + p + '/download', 'w+') as file:\n file.write(answer['dlink'])\n\nmsg['list'] = projects\nsleep(0.5)\nsave('status_cloud','ready')\nreturn msg, None\n", + "outputs": 2, + "x": 320, + "y": 180, + "wires": [ + [ + "ea54fcc2.cfcc2", + "b42e061fb1f1f3d7" + ], + [ + "6434e713f088012b" + ] + ] + }, + { + "id": "372e95797a3f2f3b", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "limit :)", + "func": "from time import sleep\n\nmsg2={}\nmsg2['enabled'] = True\n\nmsg['enabled'] = False\nnode.send(msg)\n\nwait = 15\n\nfor i in range (wait):\n msg['text'] = ' ('+ str(wait - i)+')'\n node.send(msg)\n\nmsg['enabled'] = True\nmsg['text']=\"\"\n\n\nreturn msg", + "outputs": 1, + "x": 90, + "y": 220, + "wires": [ + [ + "573edbfdb7500ddc" + ] + ] + }, + { + "id": "573edbfdb7500ddc", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 230, + "y": 220, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "dacb1f078b624e10", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 550, + "y": 340, + "wires": [ + [ + "c8d65cc7c2ff7c36" + ] + ] + }, + { + "id": "92c98e6ce7cd25f9", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "7a93d1e18254685c", + "bd75f33b8a57c522" + ], + "x": 35, + "y": 180, + "wires": [ + [ + "c46e10b9c201913e" + ] + ] + }, + { + "id": "3d16b3789632784d", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 770, + "y": 500, + "wires": [ + [] + ] + }, + { + "id": "6434e713f088012b", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "Terms", + "x": 470, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "c8d65cc7c2ff7c36", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "del", + "func": "import os\nimport shutil\n\ndir=\"/home/pi/OpenScan/scans/\"\n\nif msg['payload']==\"No\":\n return\n\nfor i in os.listdir(dir):\n if not os.path.isdir(dir + i):\n os.remove(dir + i)\n\n\ndir=\"/home/pi/OpenScan/scans/preview/\"\n\nfor i in os.listdir(dir):\n os.remove(dir + i)\n\nreturn msg\n", + "outputs": 1, + "x": 690, + "y": 340, + "wires": [ + [ + "a4f09e25.02569" + ] + ] + }, + { + "id": "f4e9a4bd79b4221f", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.payload = 'Are you sure to delete ALL saved image sets? This can not be undone!'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 340, + "wires": [ + [ + "dacb1f078b624e10" + ] + ] + }, + { + "id": "2806bf08ea21216d", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.Set=global.get('set')['Set']\nmsg.payload = 'Are you sure to delete ' + msg.Set + '?'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 380, + "wires": [ + [ + "15de0ebb.616d61" + ] + ] + }, + { + "id": "61990987acd0f263", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "6c6ef2255a7d39e5" + ], + "x": 45, + "y": 60, + "wires": [ + [ + "51579603bce21e98" + ] + ] + }, + { + "id": "e8e488a6dd5d0b33", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "Download", + "order": 8, + "width": 3, + "height": 1, + "format": "\n
Download\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 880, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "0c387c0291d6c131", + "type": "function", + "z": "80a3942785a26c29", + "name": "msg", + "func": "msg.download = '/scans/' + String(msg.payload.Set)\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 750, + "y": 260, + "wires": [ + [ + "e8e488a6dd5d0b33" + ] + ] + }, + { + "id": "e5f38b4a07a5e278", + "type": "link in", + "z": "80a3942785a26c29", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 655, + "y": 220, + "wires": [ + [ + "834046a4.647938" + ] + ] + }, + { + "id": "e434ef42bd6b92e8", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "upload2", + "order": 7, + "width": 3, + "height": 1, + "format": "upload", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 460, + "wires": [ + [ + "f6bd1a04.470838" + ] + ] + }, + { + "id": "c46e10b9c201913e", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "refresh", + "order": 2, + "width": 4, + "height": 1, + "format": "refresh{{msg.text}}", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 160, + "y": 180, + "wires": [ + [ + "372e95797a3f2f3b", + "4d99c601c9881680" + ] + ] + }, + { + "id": "d5d840183025d91b", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del set", + "order": 11, + "width": 2, + "height": 1, + "format": "delete set", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 380, + "wires": [ + [ + "2806bf08ea21216d" + ] + ] + }, + { + "id": "ab9e90ab5a53a0dd", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "del ", + "order": 10, + "width": 2, + "height": 1, + "format": "delete all", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 270, + "y": 340, + "wires": [ + [ + "f4e9a4bd79b4221f" + ] + ] + }, + { + "id": "478994f671a3907d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "combine", + "order": 9, + "width": 2, + "height": 1, + "format": "combine", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 280, + "y": 540, + "wires": [ + [ + "51bfd0fb7b1d292e" + ] + ] + }, + { + "id": "189c1eed09624a7b", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "combine = global.get('combine')\ncombine_set = global.get('set').Set\n\nif (combine === true && global.get('combine_set') !== combine_set){\n msg.set1 = global.get('combine_set')\n msg.set2 = combine_set\n global.set('combine', false)\n msg.topic = 'Combine the following two sets:'\n msg.payload = msg.set1 + '
' + msg.set2 + '
FILES WILL BE MERGED INTO ON FILE!'\n return msg\n}\nglobal.set('combine_set' , combine_set)\n\n", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 580, + "wires": [ + [ + "1493398979a63775" + ] + ] + }, + { + "id": "51bfd0fb7b1d292e", + "type": "function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "global.set('combine', true)\ncombine_set = global.get('set').Set\nmsg.topic = 'Merge two sets into one (can not be undone)!'\nmsg.payload = combine_set\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "da325be8e74179be", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "combine", + "func": "from os.path import getsize\nfrom shutil import copy\nfrom os import rename, remove\nimport zipfile as z\nfrom OpenScan import save\n\nfrom time import sleep\n\nif msg['payload'] != 'OK':\n return\n\nbasepath = '/home/pi/OpenScan/scans/'\ntmp1 = basepath + msg['set1']\ntmp2 = basepath + msg['set2']\n\nif getsize(tmp1) > getsize(tmp2):\n set1 = tmp1\n set2 = tmp2\nelse:\n set1 = tmp2\n set2 = tmp1\n\nzips = [set1, set2]\n\nwith z.ZipFile(set1, 'a') as z1:\n z2 = z.ZipFile(set2, 'r')\n i = 0\n for n in z2.namelist():\n i += 1\n n2 = n\n save('status_cloud','writing ' + str(i) + '/' + str(len(z2.namelist())))\n while 'X'+n in z1.namelist():\n n = 'X' + n\n z1.writestr('X'+n, z2.open(n2).read())\nsave('status_cloud','ready')\n\nos.rename(set1, set1[:-4] + 'X.zip')\nos.remove(set2)\n\nreturn msg", + "outputs": 1, + "x": 560, + "y": 580, + "wires": [ + [ + "ed35109311335099" + ] + ] + }, + { + "id": "ed35109311335099", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "809c9427e14e2448", + "2f4c0f98.dee2" + ], + "x": 655, + "y": 580, + "wires": [] + }, + { + "id": "1493398979a63775", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Combine", + "x": 420, + "y": 580, + "wires": [ + [ + "da325be8e74179be" + ] + ] + }, + { + "id": "ada1b6f7cccc9344", + "type": "link out", + "z": "80a3942785a26c29", + "name": "combine", + "mode": "link", + "links": [ + "6dd356510c446cf4" + ], + "x": 835, + "y": 180, + "wires": [] + }, + { + "id": "6dd356510c446cf4", + "type": "link in", + "z": "80a3942785a26c29", + "name": "combine", + "links": [ + "ada1b6f7cccc9344" + ], + "x": 175, + "y": 580, + "wires": [ + [ + "189c1eed09624a7b" + ] + ] + }, + { + "id": "b42e061fb1f1f3d7", + "type": "link out", + "z": "80a3942785a26c29", + "name": "", + "mode": "link", + "links": [ + "397ab7f44b893c89", + "3876d5cbd248592b" + ], + "x": 435, + "y": 140, + "wires": [] + }, + { + "id": "b99505440832439f", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "diskspace", + "func": "from subprocess import getoutput\nfrom OpenScan import load_int\n\ndiskspace_threshold = load_int('diskspace_threshold')\n\ndiskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n\navailable = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n\n\nif available < diskspace_threshold:\n msg['topic'] = 'Low diskspace remaining! ('+str(available)+'MB)' \n msg['payload'] = 'Please delete some/all locally stored files.'\n msg['color'] = 'red'\n return msg\n", + "outputs": 1, + "x": 800, + "y": 100, + "wires": [ + [ + "92047434f8e9f927" + ] + ] + }, + { + "id": "92047434f8e9f927", + "type": "ui_toast", + "z": "80a3942785a26c29", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 950, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "f3662f8c7d3d7a2d", + "type": "delay", + "z": "80a3942785a26c29", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "minute", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "allowrate": false, + "outputs": 1, + "x": 650, + "y": 100, + "wires": [ + [ + "b99505440832439f" + ] + ] + }, + { + "id": "51579603bce21e98", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "read", + "func": "from OpenScan import load_str\nfrom os import listdir, path\n\nstatus = load_str('status_cloud')\n\nif status[0:9] == 'uploading':\n progress = load_str('status_uploadprogress')[-6:]\n if progress[-1:] == '%':\n status = status + ' (' + progress + ')'\n\nif status[0:7] == 'zipping':\n path1 = '/home/pi/OpenScan/tmp/split/'\n files = listdir(path1)\n size1 = 0\n for file in files:\n size1 += path.getsize(path1+file)\n size2 = path.getsize('/home/pi/OpenScan/scans/'+ files[0][:-2])\n \n status = 'zipping files (' + str(float(int(1000*size1/size2))/10) + '%)'\n\nmsg['status'] = status\nreturn msg\n", + "outputs": 1, + "x": 130, + "y": 60, + "wires": [ + [ + "952ce286.4ffd4" + ] + ] + }, + { + "id": "9a5baae623355f9d", + "type": "ui_template", + "z": "80a3942785a26c29", + "group": "db43d646.2074c8", + "name": "preview", + "order": 6, + "width": 6, + "height": 6, + "format": "
\n\n\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 1020, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "85839a17fb7b58b9", + "type": "python3-function", + "z": "80a3942785a26c29", + "name": "preview", + "func": "from time import time\nimport os\n\npath = '/home/pi/OpenScan/scans/preview/'\nimage = os.path.basename(msg['payload']['Set'])[:-4] +'.jpg'\n\nmsg['payload']=\"/scans/preview/\" + image +\"?ts=\"+str(int(time()*10))\nreturn msg", + "outputs": 1, + "x": 880, + "y": 220, + "wires": [ + [ + "9a5baae623355f9d" + ] + ] + }, + { + "id": "01e4783e148c6698", + "type": "ui_table", + "z": "80a3942785a26c29", + "group": "b5fdd57b.15eda8", + "name": "", + "order": 1, + "width": 13, + "height": 7, + "columns": [ + { + "field": "Date", + "title": "Date", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Name", + "title": "Name", + "width": "150", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Photos", + "title": "Photos", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Duration", + "title": "ΔT", + "width": "60", + "align": "left", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Size", + "title": "Size", + "width": "80", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + }, + { + "field": "Status", + "title": "Status", + "width": "140", + "align": "center", + "formatter": "plaintext", + "formatterParams": { + "target": "_blank" + } + } + ], + "outputs": 1, + "cts": true, + "x": 610, + "y": 180, + "wires": [ + [ + "4082b136.dae18", + "50710948.71c308", + "834046a4.647938", + "0c387c0291d6c131" + ] + ] + }, + { + "id": "cb3437ec113e1b6f", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "SSH", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 3, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 390, + "y": 360, + "wires": [ + [ + "c24f61b87e3226dd" + ] + ] + }, + { + "id": "60fd0adce1cfeb82", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Samba", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 4, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "test2", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "441d3ef525e901da" + ] + ] + }, + { + "id": "c24f61b87e3226dd", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "ssh", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('ssh'):\n save('ssh', state)\n\nif state == True:\n os.system('/etc/init.d/ssh start')\nelse:\n os.system('/etc/init.d/ssh stop')", + "outputs": 1, + "x": 530, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "c013e836e759a085", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "4390b2ebcbbe104c", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "label": "Terms Of Use", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 120, + "y": 320, + "wires": [ + [ + "b78346ca3ce70c68" + ] + ] + }, + { + "id": "f0d8dbcca76a1926", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "Agree", + "cancel": "Disagree", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 410, + "y": 320, + "wires": [ + [ + "e95b86cbac1b03b9" + ] + ] + }, + { + "id": "34374044c0030625", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "General", + "group": "4390b2ebcbbe104c", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

General Settings

Terms Of Use

In order to use the OpenScanCloud, please read the terms of use as files will be transmitted from your device to the OpenScan Servers.

SSH

SSH can be used to access the Raspberry Pi and modify core files of the operating system. Please deactivate, if you do not want to use this feature.

If you want to use it, the default user is pi, password: raspberry. Please change the password immediately. 

Samba

Samba s a network local file sharing server, which allows accessing the Raspberry Pi's file system through the explorer (and other programs like FileZilla). You can use it to transfer custom photo sets to the device in order to use the OpenScanCloud. Therefore, you need to transfer the zip file containing your photos to the following folder /OpenScan/scans/

You can access the Raspberry Pis file system by inserting the following line into your Windows explorer: 

\\\\OpenScan/PiShare/OpenScan/scans/

username: pi, password: raspberry

Please deactivate the local file sharing if you do not intend to use it

Advanced Settings

Enable a ton of additional settings, which should be changed only if you know what you are doing ;)

Model

Device model you are using: OpenScan Mini or OpenScan Classic. Setting the device affects the settings of the motor (gear ratio, acceleration, speed). You can change those values manually in the advanced settings.

Camera

A wide range of camera modules is supported (Pi camera v1.3, v2.1, HQ, Arducam IMX519, IMX290, IMX378, OV9281). If you encounter any issues with those models, please check the orientation of the camera ribbon cable and its connectors.

DSLR (gphoto) - connect a wide range of DSLR cameras to the device through USB. See GPhoto for a full list of supported devices.

External camera - triggering any camera through an isolated GPIO signal on the front side of the pi shield.

Shutdown/Reboot

Always use the shutdown button before you power off your Raspberry Pi.

Restore Default Settings

In case you want to restore the default settings

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "b2b6bf23c9989133", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Pinout", + "group": "70d0be671bf03ca7", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Pinout

ONLY CHANGE THE PINOUT IF YOU ARE ABSOLUTELY SURE! CHANGES CAN DAMAGE THE RASPBERRY PI AND ANY PERIPHERALS!


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 220, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "441d3ef525e901da", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "smb", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('smb'):\n save('smb', state)\nif state == True:\n os.system('/etc/init.d/smbd start')\nelse:\n os.system('/etc/init.d/smbd stop')\n\n\n", + "outputs": 1, + "x": 530, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "3256bab150113a48", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Motor", + "group": "7a3279eea439bcdd", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Motor Settings

Turntable Mode

Activate turntable mode in order to deactivate the rotor. The routine will only move the turntable and take a given number of photos.

Rotor - Start Angle, Min and Max Angle

Since this version of OpenScan does not have an endstop (yet), it is necessary to tell the device its position when the routine is being started. 0° corresponds to the horizontal (natural) orientation.

After that, the device will equally space the image positions between angle min and angle max.

Rotor/Turntable

Steps per rotation -  defines the number of steps it takes to move the axis 360°. It is defined by A*B*C, where A is the number of steps for one revolution of the given stepper motor (normally 200), B is the microstepping used (normally 16), and C the gear ratio (1 for the turntable and 15 or 5,33 for the OpenScan Mini and Classic respectively)

Delay - time in microseconds between each step of the motor. Lower this value if the movement is too fast

Acceleration - a factor defining how fast the delay time between each step is being changed during acceleration and deceleration phases. Lower this value in order to make the movement smoother.

Acceleration ramp - the number of steps allowed for the acceleration processes. Increase this value, if you want smoother movement.

Manual Angle - Defines the degree value for the manual movement through the arrow buttons in the scan menu

Direction - If needed, reverse the movement (in case the arrow buttons and movement do not correspond). Alternatively, you can flip the motor cable 180° (BUT MAKE SURE TO POWER OFF THE DEVICE!)


", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 430, + "y": 140, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "7a186669a17daa71", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "camera", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Camera Settings

Jpeg quality

Value in percent, which usually does not need to be changed.

Downscale Preview

The preview image has to be scaled down depending on your network speed. If you want to have a higher quality preview image, you can increase this value, which defines the maximal width/height value. If the value is too high, the preview window might not update

Image Rotation

Change the image rotation, if needed.

Timeout

Defines the time in seconds, when the libcamera command (used for the camera modules) will timeout. Increase this value, if the camera does not get triggered in each position.

Delay Before/After

A fixed delay in seconds before and/or after a photo is taken. Increase this value when the photos have visual motion blur.

AWBG, Gain, Contrast, Saturation

Under most circumstances, you do not need to touch these values.

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 420, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "edac7dd292e7e486", + "type": "comment", + "z": "e43a27722b508115", + "name": "General Settings", + "info": "", + "x": 120, + "y": 280, + "wires": [] + }, + { + "id": "161b52034e578ee2", + "type": "comment", + "z": "e43a27722b508115", + "name": "Network", + "info": "", + "x": 100, + "y": 720, + "wires": [] + }, + { + "id": "f6d6cc35679ede63", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "more sets", + "label": "Advanced Settings", + "tooltip": "", + "group": "4390b2ebcbbe104c", + "order": 5, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 400, + "y": 480, + "wires": [ + [ + "f06a7bcad524e9f9" + ] + ] + }, + { + "id": "29745a36fc157f3f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "more sets", + "func": "from OpenScan import save\n\nif msg['payload'] != 'OK':\n msg['payload'] = False\n return None,msg\n \nsave('advanced_settings', True)\n\nreturn msg", + "outputs": 2, + "x": 820, + "y": 480, + "wires": [ + [ + "8750ad979e9ea246" + ], + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "bf23328f9fb11b22", + "type": "ui_ui_control", + "z": "e43a27722b508115", + "name": "change visibility", + "events": "all", + "x": 600, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "b37be1d222bc70c9", + "type": "inject", + "z": "e43a27722b508115", + "name": "1s_repeater", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "1", + "crontab": "", + "once": true, + "onceDelay": "2", + "topic": "", + "payload": "", + "payloadType": "date", + "x": 150, + "y": 60, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "89eedf29b404f750", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "load advanced", + "func": "from OpenScan import load_bool\n\nif load_bool('advanced_settings') == False:\n msg['payload']={\"group\":{\"hide\":[\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\"]}}\nelse:\n msg['payload']={\"group\":{\"hide\":[],\"show\":[\"Settings_General\",\"Settings_Network\",\"Settings_OpenScanCloud\",\"Settings_Camera\",\"Settings_Motor\",\"Settings_Pinout\",]}}\n\nupdate = load_bool('updateable')\n\nmsg2 = {}\n\nif update == True:\n msg2['payload'] = {\"group\":{\"show\":[\"OpenScan_Update\"]}}\nelif update == False:\n msg2['payload'] = {\"group\":{\"hide\":[\"OpenScan_Update\"]}}\n\n\nreturn msg,msg2", + "outputs": 2, + "x": 360, + "y": 60, + "wires": [ + [ + "bf23328f9fb11b22" + ], + [ + "bf23328f9fb11b22" + ] + ] + }, + { + "id": "2050de5d9e02f69f", + "type": "comment", + "z": "e43a27722b508115", + "name": "Info Texts", + "info": "", + "x": 100, + "y": 140, + "wires": [] + }, + { + "id": "ded3086945a6d4b5", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "check ip address", + "func": "import socket\nimport subprocess\n\ntestIP = \"8.8.8.8\"\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ns.connect((testIP, 0))\nipaddr = s.getsockname()[0]\nhost = socket.gethostname()\n\nmsg['ip']=ipaddr\n\nreturn msg", + "outputs": 1, + "x": 250, + "y": 940, + "wires": [ + [ + "3cfe464506f46ecd" + ] + ] + }, + { + "id": "3cfe464506f46ecd", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Your local IP:", + "format": "{{msg.ip}}", + "layout": "row-spread", + "className": "", + "x": 430, + "y": 940, + "wires": [] + }, + { + "id": "bd206ad109831e6a", + "type": "comment", + "z": "e43a27722b508115", + "name": "OpenScanCloud", + "info": "", + "x": 120, + "y": 1260, + "wires": [] + }, + { + "id": "b70a9a665c1e4d36", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Cloud-settings", + "group": "12b719cba49817c9", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

OpenScanCloud

OpenScanCloud is a free/donation-based cloud processing service, which will convert your photos into 3d models using latest photogrammetry technology. Feel free to support the project with a small donation at BuyMeACoffee.

The only requirement to use this service is a one-time, free-of-charge registration (which is solely an anti-spam measure). By filling out the registration form, you will receive an individual access token.

Register

In order to use the OpenScanCloud, you will have to enter your name and email. It might take 1-3 days to create the access token, which will be sent to your mail address. Please check your spam folder.

Enter Token

Please enter your individual token here in order to activate the cloud functionality. The token will be verified immediately. In case of any problems, please contact cloud@openscan.eu

Token

A shorted version of your token will be displayed here. Please include a copy of this shorted token in any support requests cloud@openscan.eu

Credit (GB)

Each token comes with a given amount of 'credit' which is another measure against spam. The given number in Gigabyte indicates the amount of data, that you can process on the servers. 

IMPORTANT: The credit can be increased at any time by sending a (nice) mail to cloud@openscan.eu

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 740, + "y": 260, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "c9f0566601a3e130", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Number of Photos:", + "format": "{{msg.limit_photos}}", + "layout": "row-spread", + "className": "", + "x": 410, + "y": 1400, + "wires": [] + }, + { + "id": "9bd86d27ea499a2a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "Max. Filesize (GB):", + "format": "{{msg.limit_filesize}}", + "layout": "row-spread", + "className": "", + "x": 390, + "y": 1440, + "wires": [] + }, + { + "id": "2c37f7030810d234", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "12b719cba49817c9", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Credit (GB):", + "format": "{{msg.credit}}", + "layout": "row-spread", + "className": "", + "x": 370, + "y": 1480, + "wires": [] + }, + { + "id": "f40286c18afd4501", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "save", + "func": "import requests\nimport os\nfrom OpenScan import save, OpenScanCloud\n\nif msg['payload']!=\"Yes\":\n return None,msg\n\ntry:\n r = OpenScanCloud('getTokenInfo', {'token':msg['token']})\n if r.status_code != 200:\n msg['payload'] = 'Could not verify token'\n return msg \n \n msg1 = r.json()\n \n save('osc_credit',msg1['credit'])\n save('osc_limit_filesize',msg1['limit_filesize'])\n save('osc_limit_photos',msg1['limit_photos'])\n msg1['enabled'] = True\nexcept:\n pass\n\nsave('token',msg['token'])\n \nmsg['payload'] = 'Token verified and saved'\nreturn msg, msg1", + "outputs": 2, + "x": 750, + "y": 1340, + "wires": [ + [ + "455a5266017ea121", + "50f73cee213ec05c" + ], + [ + "264eece408043021" + ] + ] + }, + { + "id": "455a5266017ea121", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "", + "name": "", + "x": 890, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "c368df68593bc2bf", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Token", + "tooltip": "", + "group": "12b719cba49817c9", + "order": 2, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 350, + "y": 1360, + "wires": [ + [ + "18fd1afa768187b3" + ] + ] + }, + { + "id": "18fd1afa768187b3", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "Save?", + "func": "msg['token'] = msg['payload']\n\nif len(msg['payload'])>=14:\n \n msg[\"payload\"]='Save and verify token: ' + msg['payload']\n return msg\nelse:\n return None,msg", + "outputs": 2, + "x": 470, + "y": 1360, + "wires": [ + [ + "418aea2ec65573a0" + ], + [ + "9792c89c5f4429f9" + ] + ] + }, + { + "id": "f90a98899b7a71d0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "text", + "func": "from OpenScan import load_str\n\ntoken = load_str('token')[0:8]\nmsg['payload']= token + '...'\nif len(token)==0:\n msg['payload']=\"enter token\"\nreturn msg", + "outputs": 1, + "x": 230, + "y": 1360, + "wires": [ + [ + "c368df68593bc2bf" + ] + ] + }, + { + "id": "b4c843620c251c43", + "type": "link in", + "z": "e43a27722b508115", + "name": "token", + "links": [ + "960912e90ba5b5bc", + "50f73cee213ec05c", + "9792c89c5f4429f9", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1360, + "wires": [ + [ + "f90a98899b7a71d0" + ] + ] + }, + { + "id": "418aea2ec65573a0", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 1340, + "wires": [ + [ + "f40286c18afd4501" + ] + ] + }, + { + "id": "9792c89c5f4429f9", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "b4c843620c251c43" + ], + "x": 555, + "y": 1380, + "wires": [] + }, + { + "id": "264eece408043021", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "5d267acc10020091", + "3876d5cbd248592b" + ], + "x": 835, + "y": 1380, + "wires": [] + }, + { + "id": "3876d5cbd248592b", + "type": "link in", + "z": "e43a27722b508115", + "name": "OSCparameters", + "links": [ + "960912e90ba5b5bc", + "264eece408043021", + "b42e061fb1f1f3d7", + "50eeb3e362f9027f" + ], + "x": 75, + "y": 1400, + "wires": [ + [ + "5daca3ec47f8e7fc" + ] + ] + }, + { + "id": "50f73cee213ec05c", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "links": [ + "b4c843620c251c43", + "5d267acc10020091" + ], + "x": 835, + "y": 1340, + "wires": [] + }, + { + "id": "95578e54a9b61cba", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 250, + "y": 1540, + "wires": [ + [ + "d7a5693da7855da8" + ] + ] + }, + { + "id": "d7a5693da7855da8", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import re\n\nif msg['payload'] == 'Cancel':\n return\n\nmail = msg['payload']\nemail_regex = re.compile(r\"[^@]+@[^@]+\\.[^@]+\")\n\nif email_regex.match(mail) != None:\n msg['mail'] = mail\n msg['topic'] = 'OpenScanCloud Registration (2/3)'\n msg['payload'] = 'Enter your first name'\n return msg\nmsg['payload'] = 'invalid input'\nreturn None,msg\n", + "outputs": 2, + "x": 390, + "y": 1540, + "wires": [ + [ + "2b02b97dd1614e52" + ], + [ + "183a629accb417b1" + ] + ] + }, + { + "id": "183a629accb417b1", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1580, + "wires": [ + [] + ] + }, + { + "id": "2b02b97dd1614e52", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 530, + "y": 1540, + "wires": [ + [ + "3e4c15d7b538f816" + ] + ] + }, + { + "id": "3bf622f344172721", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "SUBMIT", + "cancel": "Cancel", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 810, + "y": 1540, + "wires": [ + [ + "e431cb2b8d217cee" + ] + ] + }, + { + "id": "e431cb2b8d217cee", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "import requests\nimport os\nfrom OpenScan import OpenScanCloud\n\nif msg['payload'] == 'Cancel':\n return\n\nmsg['lastname'] = msg['payload']\n\nmsg2 = {}\n\nfor i in ['forename','lastname','mail']:\n msg2[i] = msg[i]\n\nr = OpenScanCloud('requestToken',msg2)\n\nstatus = r.status_code\n\nmsg['topic'] = 'OpenScanCloud Registration - Success'\nmsg['payload'] = 'registration done, you will get an email with your token within the next one or two days :)'\n\nif status != 200:\n msg['topic'] = 'OpenScanCloud Registration - Failed'\n msg['payload'] = 'Registration failed, please try again.'\n\nmsg['status'] = status\n\nreturn msg", + "outputs": 1, + "x": 950, + "y": 1540, + "wires": [ + [ + "106874534890f229" + ] + ] + }, + { + "id": "a38d7fde5c73210f", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Register", + "group": "12b719cba49817c9", + "order": 6, + "width": 2, + "height": 1, + "passthru": false, + "label": "Register", + "tooltip": "testtesttest", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "Please enter your email address:", + "payloadType": "str", + "topic": "Requesting an OpenScanCloud Token", + "topicType": "str", + "x": 100, + "y": 1540, + "wires": [ + [ + "95578e54a9b61cba" + ] + ] + }, + { + "id": "106874534890f229", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 1090, + "y": 1540, + "wires": [ + [] + ] + }, + { + "id": "5daca3ec47f8e7fc", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import load_int\n\nmsg = {}\n\ntry:\n msg['credit'] = float(int(load_int('osc_credit')/10000000))/100\n msg['limit_filesize'] = float(int(load_int('osc_limit_filesize')/10000000))/100\n msg['limit_photos'] = load_int('osc_limit_photos')\n return msg\nexcept:\n pass", + "outputs": 1, + "x": 230, + "y": 1400, + "wires": [ + [ + "c9f0566601a3e130", + "9bd86d27ea499a2a", + "2c37f7030810d234" + ] + ] + }, + { + "id": "f34de19d4cf810a9", + "type": "comment", + "z": "e43a27722b508115", + "name": "Motor", + "info": "", + "x": 90, + "y": 1740, + "wires": [] + }, + { + "id": "26c2b58e21f97475", + "type": "comment", + "z": "e43a27722b508115", + "name": "Camera", + "info": "", + "x": 90, + "y": 2500, + "wires": [] + }, + { + "id": "a8ec972bad47a9a8", + "type": "comment", + "z": "e43a27722b508115", + "name": "Pinout", + "info": "", + "x": 90, + "y": 2960, + "wires": [] + }, + { + "id": "b03e8b51187e88eb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "Rotor_delay (ms)", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 16, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 450, + "y": 2100, + "wires": [ + [ + "11fd3363416433f9" + ] + ] + }, + { + "id": "6aae9d4fddf08cc0", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt delay", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 30, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.01", + "max": "0.2", + "step": "0.005", + "className": "", + "x": 420, + "y": 2340, + "wires": [ + [ + "e50492d1e18f43c6" + ] + ] + }, + { + "id": "543e1690693acbeb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 18, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 420, + "y": 2140, + "wires": [ + [ + "e8b24efb0f30288e" + ] + ] + }, + { + "id": "9a56c087d941f1da", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 20, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "100", + "max": "5000", + "step": "100", + "className": "", + "x": 440, + "y": 2180, + "wires": [ + [ + "29f576be9e292232" + ] + ] + }, + { + "id": "dfdebe10dbf0e198", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotor_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 14, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 460, + "y": 2060, + "wires": [ + [ + "78e256083f59f66f" + ] + ] + }, + { + "id": "af8dfe78cbd0c301", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 19, + "width": 3, + "height": 1, + "name": "rotor Accramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2140, + "wires": [] + }, + { + "id": "ee4b8908a5b83880", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_Steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 810, + "y": 2180, + "wires": [] + }, + { + "id": "c4deaa38c1b0adbf", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 17, + "width": 3, + "height": 1, + "name": "rotor Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2100, + "wires": [] + }, + { + "id": "baec873a95fff48a", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 15, + "width": 3, + "height": 1, + "name": "rotor_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 770, + "y": 2060, + "wires": [] + }, + { + "id": "355e89ab4e5484e4", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 26, + "width": 6, + "height": 1, + "name": "tt", + "label": "TURNTABLE", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 2300, + "wires": [] + }, + { + "id": "10687d331a732790", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_acc", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 32, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0.1", + "max": "2", + "step": "0.1", + "className": "", + "x": 410, + "y": 2380, + "wires": [ + [ + "af88b9da72917d62" + ] + ] + }, + { + "id": "721b9680a3fa460e", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_accramp", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 34, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "500", + "step": "1", + "className": "", + "x": 430, + "y": 2420, + "wires": [ + [ + "b1b4678827d3a6dd" + ] + ] + }, + { + "id": "c6642c7470d3820c", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "tt_stepsperrotation", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 28, + "width": 3, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 450, + "y": 2300, + "wires": [ + [ + "eef89545ec0f6aa8" + ] + ] + }, + { + "id": "18e5918748660109", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 33, + "width": 3, + "height": 1, + "name": "ttAccramp", + "label": "Acceleration ramp", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2420, + "wires": [] + }, + { + "id": "8e805244dc1899e8", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 27, + "width": 3, + "height": 1, + "name": "tt_steps per Rotation", + "label": "Steps per Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 800, + "y": 2300, + "wires": [] + }, + { + "id": "a09e5fbea861bfb1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 31, + "width": 3, + "height": 1, + "name": "tt Acc", + "label": "Acceleration", + "format": "", + "layout": "row-left", + "className": "", + "x": 750, + "y": 2380, + "wires": [] + }, + { + "id": "7b06448b3b222011", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 29, + "width": 3, + "height": 1, + "name": "tt_delay", + "label": "Delay", + "format": "", + "layout": "row-left", + "className": "", + "x": 760, + "y": 2340, + "wires": [] + }, + { + "id": "0dfc86d90258f9bb", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 22, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 430, + "y": 2220, + "wires": [ + [ + "c4b5a38c5c1df3d2" + ] + ] + }, + { + "id": "9319d7d4f34c6d22", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 21, + "width": 3, + "height": 1, + "name": "rotor_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 770, + "y": 2220, + "wires": [] + }, + { + "id": "1610895f430b9aca", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 36, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "180", + "step": "1", + "className": "", + "x": 420, + "y": 2460, + "wires": [ + [ + "0f3367983bb8e159" + ] + ] + }, + { + "id": "96a9febc0928b6f0", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 35, + "width": 3, + "height": 1, + "name": "tt_angle", + "label": "Manual angle", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2460, + "wires": [] + }, + { + "id": "e2c5ea8c16a5ea32", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 2, + "width": 6, + "height": 1, + "name": "rotor", + "label": "ROTOR", + "format": "", + "layout": "row-center", + "className": "", + "x": 90, + "y": 1820, + "wires": [] + }, + { + "id": "277037c4716d85bf", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "tt_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 38, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 410, + "y": 2500, + "wires": [ + [ + "c9d2e31514def4fc" + ] + ] + }, + { + "id": "1361134e9847f003", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_dir", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 24, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "1", + "className": "", + "x": 420, + "y": 2260, + "wires": [ + [ + "523717b0f218a5fd" + ] + ] + }, + { + "id": "6b0d58943ecb8bb2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 37, + "width": 3, + "height": 1, + "name": "tt_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2500, + "wires": [] + }, + { + "id": "08f93dd2aeedb391", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 23, + "width": 3, + "height": 1, + "name": "rotor_dir", + "label": "Direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2260, + "wires": [] + }, + { + "id": "46b91bef44714366", + "type": "link in", + "z": "e43a27722b508115", + "name": "advanced settings", + "links": [ + "8750ad979e9ea246" + ], + "x": 95, + "y": 100, + "wires": [ + [ + "89eedf29b404f750" + ] + ] + }, + { + "id": "8750ad979e9ea246", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "46b91bef44714366" + ], + "x": 955, + "y": 480, + "wires": [] + }, + { + "id": "2522f888dc58972f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_before", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 7, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 430, + "y": 2600, + "wires": [ + [ + "5c752757090c49d2" + ] + ] + }, + { + "id": "30e8df3d616512d8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_gain", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 11, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "10", + "step": "0.1", + "className": "", + "x": 400, + "y": 2640, + "wires": [ + [ + "a1769f0277834f6d" + ] + ] + }, + { + "id": "d855d926df89d65b", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_contrast", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 13, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2760, + "wires": [ + [ + "1a8b0ba21b4f3005", + "654bc70a18820828" + ] + ] + }, + { + "id": "7617517dc8ba2859", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_saturation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 15, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "5", + "step": "0.1", + "className": "", + "x": 420, + "y": 2800, + "wires": [ + [ + "dc8fc962ff7d594b", + "e64feb03a791ca33" + ] + ] + }, + { + "id": "cbaa23c34e10fae1", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_jpeg_q", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 3, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "1", + "max": "100", + "step": "1", + "className": "", + "x": 410, + "y": 2840, + "wires": [ + [ + "00e7836ccb3c4d0c" + ] + ] + }, + { + "id": "bbe443b039a14e21", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 6, + "width": 3, + "height": 1, + "name": "delay_before", + "label": "Delay before", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2600, + "wires": [] + }, + { + "id": "d320ed3d701e6cc2", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 10, + "width": 3, + "height": 1, + "name": "gain", + "label": "Gain", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2640, + "wires": [] + }, + { + "id": "f5834dd4646c8af9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 12, + "width": 3, + "height": 1, + "name": "contrast", + "label": "Contrast", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2760, + "wires": [] + }, + { + "id": "ae9a4e19469813ef", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 14, + "width": 3, + "height": 1, + "name": "saturation", + "label": "Saturation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2800, + "wires": [] + }, + { + "id": "bd629d0d31233c8b", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 2, + "width": 3, + "height": 1, + "name": "jpegQ", + "label": "Jpeg Quality", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 2840, + "wires": [] + }, + { + "id": "e89f61dbe6a6cffe", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ext", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 3, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3000, + "wires": [ + [ + "885bc559fafec5f2" + ] + ] + }, + { + "id": "ece38cb172a12d75", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 2, + "width": 4, + "height": 1, + "name": "ext", + "label": "External Camera", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3000, + "wires": [] + }, + { + "id": "70014da0b6ab6698", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3040, + "wires": [ + [ + "f70321c96bf81360" + ] + ] + }, + { + "id": "29634ea5f6d666df", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 4, + "width": 4, + "height": 1, + "name": "light1", + "label": "Light 1", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3040, + "wires": [] + }, + { + "id": "2544963852c6881a", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "light2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 7, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3080, + "wires": [ + [ + "95e1603bbd06a69d" + ] + ] + }, + { + "id": "27903533cd85a59e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 6, + "width": 4, + "height": 1, + "name": "light2", + "label": "Light 2", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3080, + "wires": [] + }, + { + "id": "a1394401246eb735", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotordir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 9, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3120, + "wires": [ + [ + "a8f92ea6bf394640" + ] + ] + }, + { + "id": "bc0aa4bacdfa94ea", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 8, + "width": 4, + "height": 1, + "name": "rotordir", + "label": "Rotor direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3120, + "wires": [] + }, + { + "id": "f15ca4518b5f223e", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotorstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 11, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3160, + "wires": [ + [ + "06397bb46b3bb541" + ] + ] + }, + { + "id": "0d2924b160e7e383", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 10, + "width": 4, + "height": 1, + "name": "rotorstep", + "label": "Rotor step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3160, + "wires": [] + }, + { + "id": "49900bb9047dd965", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "rotoren", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 13, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3200, + "wires": [ + [ + "687dcdc1ede11700" + ] + ] + }, + { + "id": "a4d743ca73ee1622", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 12, + "width": 4, + "height": 1, + "name": "rotoren", + "label": "Rotor enable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3200, + "wires": [] + }, + { + "id": "5a90224dc998b417", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttdir", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 15, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3240, + "wires": [ + [ + "e220740c0d38ccb0" + ] + ] + }, + { + "id": "67dc1b544c4ddf9f", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 14, + "width": 4, + "height": 1, + "name": "ttdir", + "label": "Turntable direction", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3240, + "wires": [] + }, + { + "id": "d2364ab09627fe94", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "ttstep", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 17, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 390, + "y": 3280, + "wires": [ + [ + "79d7e5a705ab813a" + ] + ] + }, + { + "id": "145b67ac40721ba6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 16, + "width": 4, + "height": 1, + "name": "ttstep", + "label": "Turntable step", + "format": "", + "layout": "row-spread", + "className": "", + "x": 730, + "y": 3280, + "wires": [] + }, + { + "id": "eef25405472acfee", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop1", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 19, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3320, + "wires": [ + [ + "12d20f2274bcc511" + ] + ] + }, + { + "id": "35eb252a41413531", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 18, + "width": 4, + "height": 1, + "name": "endstop1", + "label": "Endstop Rotor", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3320, + "wires": [] + }, + { + "id": "74e455136b5ca5dd", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "endstop2", + "label": "", + "tooltip": "", + "group": "70d0be671bf03ca7", + "order": 21, + "width": 2, + "height": 1, + "passthru": false, + "mode": "number", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 400, + "y": 3360, + "wires": [ + [ + "a4a89668ce4c9f05" + ] + ] + }, + { + "id": "3a74f653800eb831", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "70d0be671bf03ca7", + "order": 20, + "width": 4, + "height": 1, + "name": "endstop2", + "label": "Endstop Turntable", + "format": "", + "layout": "row-spread", + "className": "", + "x": 740, + "y": 3360, + "wires": [] + }, + { + "id": "5fcef1cb2e9e4788", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "confirm", + "x": 680, + "y": 480, + "wires": [ + [ + "29745a36fc157f3f" + ] + ] + }, + { + "id": "f06a7bcad524e9f9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "msg", + "func": "from OpenScan import save, load_bool\n\nif msg['payload'] == True and not load_bool('advanced_settings'):\n msg['payload'] = '''

PLEASE READ :)

\n

Modifying the advanced settings can potentially damage your device and/or the connected peripherals.

\n

Please read the given information texts carefully and only change settings, when you are sure about the consequences!

\n'''\n return msg\nelif not msg['payload']: \n save('advanced_settings', False)\n", + "outputs": 1, + "x": 530, + "y": 480, + "wires": [ + [ + "5fcef1cb2e9e4788" + ] + ] + }, + { + "id": "f455fb39039617ae", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_rotation", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 5, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "270", + "step": "90", + "className": "", + "x": 410, + "y": 2880, + "wires": [ + [ + "3019576de193d9d6" + ] + ] + }, + { + "id": "fdfbc900fe424eb9", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 4, + "width": 3, + "height": 1, + "name": "cam_rot", + "label": "Image Rotation", + "format": "", + "layout": "row-spread", + "className": "", + "x": 750, + "y": 2880, + "wires": [] + }, + { + "id": "c3699d6b9664ccca", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "dfdebe10dbf0e198" + ] + ] + }, + { + "id": "78e256083f59f66f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "0f9141b401322374", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2180, + "wires": [ + [ + "9a56c087d941f1da" + ] + ] + }, + { + "id": "29f576be9e292232", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2180, + "wires": [ + [] + ] + }, + { + "id": "23e3099b34c4e475", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2220, + "wires": [ + [ + "0dfc86d90258f9bb" + ] + ] + }, + { + "id": "c4b5a38c5c1df3d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2220, + "wires": [ + [] + ] + }, + { + "id": "79a14162ac805fac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2260, + "wires": [ + [ + "1361134e9847f003" + ] + ] + }, + { + "id": "523717b0f218a5fd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2260, + "wires": [ + [] + ] + }, + { + "id": "f5cf780f3fa8997e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2100, + "wires": [ + [ + "b03e8b51187e88eb" + ] + ] + }, + { + "id": "11fd3363416433f9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2100, + "wires": [ + [] + ] + }, + { + "id": "02060b3f3b294563", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2140, + "wires": [ + [ + "543e1690693acbeb" + ] + ] + }, + { + "id": "e8b24efb0f30288e", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2140, + "wires": [ + [] + ] + }, + { + "id": "de1ad8b27b72a5ac", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "outputs": 1, + "noerr": 4, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2300, + "wires": [ + [ + "c6642c7470d3820c" + ] + ] + }, + { + "id": "ed4d587cb4feb064", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2420, + "wires": [ + [ + "721b9680a3fa460e" + ] + ] + }, + { + "id": "5b02160c33605ae7", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2460, + "wires": [ + [ + "1610895f430b9aca" + ] + ] + }, + { + "id": "304c135ec09801e3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2500, + "wires": [ + [ + "277037c4716d85bf" + ] + ] + }, + { + "id": "a91dcbe0f9a2416a", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data) * 1000;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2340, + "wires": [ + [ + "6aae9d4fddf08cc0" + ] + ] + }, + { + "id": "6b2eb1cb95e573f9", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2380, + "wires": [ + [ + "10687d331a732790" + ] + ] + }, + { + "id": "eef89545ec0f6aa8", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2300, + "wires": [ + [] + ] + }, + { + "id": "b1b4678827d3a6dd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_accramp'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2420, + "wires": [ + [] + ] + }, + { + "id": "0f3367983bb8e159", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2460, + "wires": [ + [] + ] + }, + { + "id": "c9d2e31514def4fc", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nif (msg.payload === 1){\n content = '1'\n}\nelse{\n content = '-1'\n}\n\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2500, + "wires": [ + [] + ] + }, + { + "id": "e50492d1e18f43c6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_delay'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload / 1000)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2340, + "wires": [ + [] + ] + }, + { + "id": "af88b9da72917d62", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'tt_acc'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2380, + "wires": [ + [] + ] + }, + { + "id": "43fe948b3e7234e2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2600, + "wires": [ + [ + "2522f888dc58972f" + ] + ] + }, + { + "id": "5c752757090c49d2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_before'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2600, + "wires": [ + [] + ] + }, + { + "id": "435681b3f7625a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2640, + "wires": [ + [ + "30e8df3d616512d8" + ] + ] + }, + { + "id": "a1769f0277834f6d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_gain'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2640, + "wires": [ + [] + ] + }, + { + "id": "1de07c7d285cbaf3", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2760, + "wires": [ + [ + "d855d926df89d65b" + ] + ] + }, + { + "id": "1a8b0ba21b4f3005", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_contrast'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2760, + "wires": [ + [] + ] + }, + { + "id": "ebc9e283468eda31", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2800, + "wires": [ + [ + "7617517dc8ba2859" + ] + ] + }, + { + "id": "dc8fc962ff7d594b", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_saturation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2800, + "wires": [ + [] + ] + }, + { + "id": "60d641613527c736", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2840, + "wires": [ + [ + "cbaa23c34e10fae1" + ] + ] + }, + { + "id": "00e7836ccb3c4d0c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_jpeg_quality'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2840, + "wires": [ + [] + ] + }, + { + "id": "7f24c0c34a88ba04", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2880, + "wires": [ + [ + "f455fb39039617ae" + ] + ] + }, + { + "id": "3019576de193d9d6", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_rotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2880, + "wires": [ + [] + ] + }, + { + "id": "77bb7dc529d63a7e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3000, + "wires": [ + [ + "e89f61dbe6a6cffe" + ] + ] + }, + { + "id": "885bc559fafec5f2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_external'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3000, + "wires": [ + [] + ] + }, + { + "id": "cc6dabe017a9c8a8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3320, + "wires": [ + [ + "eef25405472acfee" + ] + ] + }, + { + "id": "12d20f2274bcc511", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3320, + "wires": [ + [] + ] + }, + { + "id": "dcb9fed8122759fd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3040, + "wires": [ + [ + "70014da0b6ab6698" + ] + ] + }, + { + "id": "f70321c96bf81360", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight1'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3040, + "wires": [ + [] + ] + }, + { + "id": "013d2057c2347a62", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3080, + "wires": [ + [ + "2544963852c6881a" + ] + ] + }, + { + "id": "95e1603bbd06a69d", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_ringlight2'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3080, + "wires": [ + [] + ] + }, + { + "id": "f88bbf11d5aa9a14", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3120, + "wires": [ + [ + "a1394401246eb735" + ] + ] + }, + { + "id": "a8f92ea6bf394640", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3120, + "wires": [ + [] + ] + }, + { + "id": "301af70731e096e5", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3160, + "wires": [ + [ + "f15ca4518b5f223e" + ] + ] + }, + { + "id": "06397bb46b3bb541", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3160, + "wires": [ + [] + ] + }, + { + "id": "0456a9ec4c236c9e", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3200, + "wires": [ + [ + "49900bb9047dd965" + ] + ] + }, + { + "id": "687dcdc1ede11700", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_rotor_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3200, + "wires": [ + [] + ] + }, + { + "id": "09d37ba08ec0f163", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3240, + "wires": [ + [ + "5a90224dc998b417" + ] + ] + }, + { + "id": "37d954a4cf7e87ea", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3280, + "wires": [ + [ + "d2364ab09627fe94" + ] + ] + }, + { + "id": "e220740c0d38ccb0", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_dir'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3240, + "wires": [ + [] + ] + }, + { + "id": "79d7e5a705ab813a", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_step'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3280, + "wires": [ + [] + ] + }, + { + "id": "21dc963d967d9c99", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 3360, + "wires": [ + [ + "74e455136b5ca5dd" + ] + ] + }, + { + "id": "a4a89668ce4c9f05", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 3360, + "wires": [ + [] + ] + }, + { + "id": "22ef66b0e2058be2", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 360, + "wires": [ + [ + "cb3437ec113e1b6f" + ] + ] + }, + { + "id": "9ce01c8ba97932c1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'smb'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 400, + "wires": [ + [ + "60fd0adce1cfeb82" + ] + ] + }, + { + "id": "81356177176eebcf", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 480, + "wires": [ + [ + "f6d6cc35679ede63" + ] + ] + }, + { + "id": "b78346ca3ce70c68", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.payload = 'This is a free piece of software and it is provided as is, without any warranty.
There might be functions that need a connection to the internet: '+\n '

By pressing GET FEATURES you agree that the shown preview image will be transfered, stored and processed via SFTP to my servers '+\n '(Thomas Megel, OpenScan, Halle, Germany). The IP address will be saved for 14 days The images might be used for further experiments (e.g. machine learning, automation ...). '+\n '

By entering a token and/or pressing UPLOAD, the device will create a connection to my servers, where the associated user information is stored (token, email, name, credit, limit_photos, limit_filesize)'+\n 'The selected image set will be uploaded to Dropbox Inc via one-time temporary upload link. The files will be saved on Dropbox Inc. for a maximum of 7 days. (+the time Dropbox Inc. will need to delete the files permanently)'+\n 'Processing will be done on my local servers, where the images get downloaded from Dropbox and processed on my workstations. The resulting 3D model will be uploaded to Dropbox and a link will be created and send to your email address from my google mail account.'+\n '

By uploading data to my servers, you agree, that I can use those images and derived 3d models for further research and to improve my services.'+\n 'The raw images and resulting 3d models will never be published without your explicit consent.'+ \n '

If you have any questions you can contact me at info@openscan.eu.'+ \n '

THE SOFTWARE IS PROVIDED AS IS WITHOUT '+\n 'WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE'+ \n 'AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,'+ \n 'WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE';\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 320, + "wires": [ + [ + "f0d8dbcca76a1926" + ] + ] + }, + { + "id": "e95b86cbac1b03b9", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var data\n\nif(msg.payload === 'Agree'){\n data = true;\n}\nelse{\n data = false;\n}\nvar file = 'terms'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nfs.writeFile(filepath+file, String(data), err => {\n if (err) {\n return msg\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "3e4c15d7b538f816", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "if (msg.payload === 'Cancel'){\n return\n}\nmsg.forename = msg.payload\nmsg.topic = 'OpenScanCloud Registration (3/3)'\nmsg.payload = 'Enter your last name'\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1540, + "wires": [ + [ + "3bf622f344172721" + ] + ] + }, + { + "id": "0f0871baf322b6d0", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1820, + "wires": [ + [ + "6ebd15c61a5ca891" + ] + ] + }, + { + "id": "f21a95a732fadae6", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 5, + "width": 3, + "height": 1, + "name": "rotor_anglemin", + "label": "Min Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1820, + "wires": [] + }, + { + "id": "acd10a4c99ee8063", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemin'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1820, + "wires": [ + [] + ] + }, + { + "id": "6ebd15c61a5ca891", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemin", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 6, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1820, + "wires": [ + [ + "acd10a4c99ee8063" + ] + ] + }, + { + "id": "3ad0f0f206e4a873", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglemax", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 8, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1860, + "wires": [ + [ + "031d7697768d0e77" + ] + ] + }, + { + "id": "3b6d759ed5be647f", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "rotor_anglestart", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 4, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "5", + "className": "", + "x": 440, + "y": 1900, + "wires": [ + [ + "be1954dd71d2c94c" + ] + ] + }, + { + "id": "edb1c8fae8b65c82", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1860, + "wires": [ + [ + "3ad0f0f206e4a873" + ] + ] + }, + { + "id": "031d7697768d0e77", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglemax'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1860, + "wires": [ + [] + ] + }, + { + "id": "462a8f3ca75fc3c8", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1900, + "wires": [ + [ + "3b6d759ed5be647f" + ] + ] + }, + { + "id": "be1954dd71d2c94c", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_anglestart'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1900, + "wires": [ + [] + ] + }, + { + "id": "3d7379753d2eda25", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 7, + "width": 3, + "height": 1, + "name": "rotor_anglemax", + "label": "Max Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1860, + "wires": [] + }, + { + "id": "9cc86d1bcae3ab4e", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 3, + "width": 3, + "height": 1, + "name": "rotor_anglestart", + "label": "Start Angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 1900, + "wires": [] + }, + { + "id": "2e9b29c70969cf01", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 135, + "y": 360, + "wires": [ + [ + "22ef66b0e2058be2", + "9ce01c8ba97932c1", + "81356177176eebcf", + "d54b85891248ba88" + ] + ] + }, + { + "id": "592ec13d8f8923a9", + "type": "link in", + "z": "e43a27722b508115", + "name": "ip address", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc", + "eb1a2387a1eeea76", + "c994c779e4bad800" + ], + "x": 85, + "y": 940, + "wires": [ + [ + "ded3086945a6d4b5", + "6ea3cdab41f20f92" + ] + ] + }, + { + "id": "cb40b9341bd22a28", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 185, + "y": 1820, + "wires": [ + [ + "0f0871baf322b6d0", + "edb1c8fae8b65c82", + "462a8f3ca75fc3c8", + "c3699d6b9664ccca", + "f5cf780f3fa8997e", + "02060b3f3b294563", + "0f9141b401322374", + "23e3099b34c4e475", + "79a14162ac805fac", + "de1ad8b27b72a5ac", + "a91dcbe0f9a2416a", + "6b2eb1cb95e573f9", + "ed4d587cb4feb064", + "5b02160c33605ae7", + "304c135ec09801e3", + "f036424d79645761", + "b7db72b7f0599ebd" + ] + ] + }, + { + "id": "d1efcd5fa9d25785", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 155, + "y": 2540, + "wires": [ + [ + "43fe948b3e7234e2", + "435681b3f7625a7e", + "1de07c7d285cbaf3", + "ebc9e283468eda31", + "60d641613527c736", + "7f24c0c34a88ba04", + "6281b2e6e081104d" + ] + ] + }, + { + "id": "da61581182b7299e", + "type": "link in", + "z": "e43a27722b508115", + "name": "enable projectname", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 135, + "y": 3000, + "wires": [ + [ + "77bb7dc529d63a7e", + "dcb9fed8122759fd", + "013d2057c2347a62", + "f88bbf11d5aa9a14", + "301af70731e096e5", + "0456a9ec4c236c9e", + "09d37ba08ec0f163", + "37d954a4cf7e87ea", + "cc6dabe017a9c8a8", + "21dc963d967d9c99" + ] + ] + }, + { + "id": "7e1c84ec516ad0a6", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Reset default", + "group": "4390b2ebcbbe104c", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "label": "Restore default settings", + "tooltip": "", + "color": "red", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "This can not be undone!", + "payloadType": "str", + "topic": "Restore default settings?", + "topicType": "str", + "x": 110, + "y": 620, + "wires": [ + [ + "53e6681d7254d484" + ] + ] + }, + { + "id": "53e6681d7254d484", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "No", + "cancel": "Yes", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 270, + "y": 620, + "wires": [ + [ + "c11e79cfa7bc10b7" + ] + ] + }, + { + "id": "c11e79cfa7bc10b7", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.overwrite = true\nif(msg.payload == \"Yes\"){\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 620, + "wires": [ + [ + "307782d10c1acdaf" + ] + ] + }, + { + "id": "307782d10c1acdaf", + "type": "link out", + "z": "e43a27722b508115", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 505, + "y": 620, + "wires": [] + }, + { + "id": "5fff689f9f8bc1ca", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "Info", + "x": 1010, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "cca3300a8f0daf4d", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Update&Info", + "group": "ddbd496e.93a288", + "order": 1, + "width": 6, + "height": 1, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Update&Log

Status

See whether new updates are available. It is highly recommended to use the latest firmware version. See OpenScan2 on Github.com for details and the source code.

Updatetype

- stable: latest well-tested and mostly bug-free version for the OpenScanMini or Classic and various cameras

- beta: stable version + some experimental and new features, which might bring joy and some new bugs as well

- mini: very simplified firmware for the OpenScanMini + Arducam IMX519

Auto-Check update availability

Perform an automated update-check after each start of the device. If the device is connected to the internet, it will get the latest files from OpenScan2 on Github.com

This option is activated by default.

Check Updates

Alternatively, you can check for updates manually at any time by pressing this button.

Download Error Log

In case you encounter any errors with your device, please download the error log text and send a copy to info@openscan.eu or create an issue on Github.com

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 750, + "y": 180, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "654bc70a18820828", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_contrast?contrast=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2720, + "wires": [ + [] + ] + }, + { + "id": "e64feb03a791ca33", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_saturation?saturation=\" + str(msg['payload']))", + "outputs": 1, + "x": 660, + "y": 2680, + "wires": [ + [] + ] + }, + { + "id": "81bd4381cd029958", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "cam_delay_after", + "label": "", + "tooltip": "", + "group": "d324f0b852c2df0a", + "order": 9, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "0", + "max": "1", + "step": "0.02", + "className": "", + "x": 440, + "y": 2560, + "wires": [ + [ + "e612073aded01a8f" + ] + ] + }, + { + "id": "0d92559980944ae3", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "d324f0b852c2df0a", + "order": 8, + "width": 3, + "height": 1, + "name": "delay_after", + "label": "Delay after", + "format": "", + "layout": "row-spread", + "className": "", + "x": 760, + "y": 2560, + "wires": [] + }, + { + "id": "6281b2e6e081104d", + "type": "function", + "z": "e43a27722b508115", + "name": "loadF", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseFloat(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 280, + "y": 2560, + "wires": [ + [ + "81bd4381cd029958" + ] + ] + }, + { + "id": "e612073aded01a8f", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'cam_delay_after'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 620, + "y": 2560, + "wires": [ + [] + ] + }, + { + "id": "e2411b49791840e0", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "reboot", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", + "outputs": 1, + "x": 270, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "01c882fcc51b349c", + "type": "link in", + "z": "e43a27722b508115", + "name": "reboot", + "links": [ + "16c76929f88df841", + "fe3a855fee9e28c6", + "09d4a9c756161e10" + ], + "x": 155, + "y": 520, + "wires": [ + [ + "e2411b49791840e0" + ] + ] + }, + { + "id": "e51dd5e5c0f050d6", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "SSID", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 4, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "ssid", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 210, + "y": 980, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "9959649037cb063b", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Password", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "password", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 220, + "y": 1020, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "1d42cb9a63409283", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "", + "label": "Country Code 2", + "tooltip": "", + "group": "8ab79a98e536e0d6", + "order": 6, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "country", + "sendOnBlur": true, + "className": "", + "topicType": "str", + "x": 240, + "y": 1060, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "84ecaafd629c0f7a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "", + "group": "8ab79a98e536e0d6", + "order": 7, + "width": 0, + "height": 0, + "passthru": false, + "label": "Connect to Wifi", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "connect", + "topicType": "str", + "x": 240, + "y": 1100, + "wires": [ + [ + "a7d233f984009e2e" + ] + ] + }, + { + "id": "6ea3cdab41f20f92", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "8ab79a98e536e0d6", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "Hotspot Mode", + "format": "{{msg.mode}}", + "layout": "row-spread", + "className": "", + "x": 240, + "y": 900, + "wires": [] + }, + { + "id": "a7d233f984009e2e", + "type": "function", + "z": "e43a27722b508115", + "name": "function 1", + "func": "if (msg.topic == \"ssid\"){\n global.set('network_ssid',msg.payload)\n}\nelse if (msg.topic == \"password\"){\n global.set('network_password',msg.payload)\n}\nelse if (msg.topic == \"country\"){\n global.set('network_country',msg.payload)\n}\nelse if (msg.topic == \"connect\"){\n msg.ssid = global.get('network_ssid')\n msg.password = global.get('network_password')\n msg.country = global.get('network_country')\n msg.payload = \"\"\n return msg\n}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 980, + "wires": [ + [ + "9b851aa999e86fd7", + "021dc780b478fee6", + "9ec0ad9fd3687e9f" + ] + ] + }, + { + "id": "65518f3d4e3095e5", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 1", + "links": [ + "200d4b9951b6e066" + ], + "x": 85, + "y": 980, + "wires": [ + [ + "e51dd5e5c0f050d6", + "9959649037cb063b", + "1d42cb9a63409283" + ] + ] + }, + { + "id": "9b851aa999e86fd7", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\nfrom time import sleep\n\nsleep(0.5)\n\nerror = \"\"\nif msg['ssid'] == \"\":\n error = \"SSID, \"\nif msg['password'] == \"\" or len(msg['password'])<8:\n error = error + \"password, \"\nif msg['country'] == \"\" or len(msg['country']) != 2:\n error = error + \"country code\"\n\nif error != \"\":\n msg['payload'] = error\n msg['topic'] = \"Invalid Input(s):\"\n if check_hotspot_mode():\n msg['mode'] = True\n else:\n msg['mode'] = False\n return msg\n\n\nmsg['result'] = add_wifi_network(msg['ssid'],msg['password'],msg['country'])\n\nsleep(3)\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nmsg['topic'] = \"Added wifi & connected\"\nmsg['payload'] = \"changes might take a moment ;)\"\n\nreturn msg", + "outputs": 1, + "x": 670, + "y": 980, + "wires": [ + [ + "c994c779e4bad800", + "11b19e9c6a4ffd8d", + "36890eb99a2ca1cf" + ] + ] + }, + { + "id": "11b19e9c6a4ffd8d", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 870, + "y": 980, + "wires": [ + [] + ] + }, + { + "id": "021dc780b478fee6", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 640, + "y": 920, + "wires": [] + }, + { + "id": "c994c779e4bad800", + "type": "link out", + "z": "e43a27722b508115", + "name": "link out 2", + "mode": "link", + "links": [ + "592ec13d8f8923a9" + ], + "x": 815, + "y": 1020, + "wires": [] + }, + { + "id": "1eef47e0074545a9", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "from OpenScan import add_wifi_network, check_hotspot_mode\n\nif check_hotspot_mode():\n msg['mode'] = True\nelse:\n msg['mode'] = False\n\nreturn msg", + "outputs": 2, + "x": 670, + "y": 1100, + "wires": [ + [ + "c994c779e4bad800", + "36890eb99a2ca1cf" + ], + [] + ] + }, + { + "id": "434b04d8a65951ce", + "type": "inject", + "z": "e43a27722b508115", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 440, + "y": 1140, + "wires": [ + [ + "1eef47e0074545a9" + ] + ] + }, + { + "id": "9ec0ad9fd3687e9f", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "bottom right", + "displayTime": "5", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "className": "", + "topic": "Adding new Wifi", + "name": "", + "x": 670, + "y": 1020, + "wires": [] + }, + { + "id": "36890eb99a2ca1cf", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 860, + "y": 940, + "wires": [] + }, + { + "id": "6b7245c3dcb694c8", + "type": "ui_slider", + "z": "e43a27722b508115", + "name": "endstop_angle", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": false, + "outs": "end", + "topic": "", + "topicType": "str", + "min": "-90", + "max": "90", + "step": "1", + "className": "", + "x": 440, + "y": 2020, + "wires": [ + [ + "85ad07b8f973bbe2" + ] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "endstop_angle", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 780, + "y": 2020, + "wires": [] + }, + { + "id": "85ad07b8f973bbe2", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2020, + "wires": [ + [] + ] + }, + { + "id": "f036424d79645761", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_angle'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2020, + "wires": [ + [ + "6b7245c3dcb694c8" + ] + ] + }, + { + "id": "253feafa5a2f8b1d", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_enable_endstop", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 10, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 1940, + "wires": [ + [ + "1916dc3fd04f0664", + "6cb92b9b9f0d6954" + ] + ] + }, + { + "id": "b7db72b7f0599ebd", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1940, + "wires": [ + [ + "253feafa5a2f8b1d" + ] + ] + }, + { + "id": "1916dc3fd04f0664", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1940, + "wires": [ + [] + ] + }, + { + "id": "de409e57a0c4bf41", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 9, + "width": 3, + "height": 1, + "name": "rotor_enable_endstop", + "label": "Enable Endstop", + "format": "", + "layout": "row-left", + "className": "", + "x": 800, + "y": 1940, + "wires": [] + }, + { + "id": "6cb92b9b9f0d6954", + "type": "function", + "z": "e43a27722b508115", + "name": "msg", + "func": "msg.enabled = msg.payload\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 1980, + "wires": [ + [ + "69516440e3997111", + "f036424d79645761" + ] + ] + }, + { + "id": "d54b85891248ba88", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'group_stack_photos'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 440, + "wires": [ + [ + "eefed04c25e3e4d6" + ] + ] + }, + { + "id": "eefed04c25e3e4d6", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Group Stack Photos", + "tooltip": "Group photos that are part of the same focus photoset", + "group": "d324f0b852c2df0a", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 440, + "y": 440, + "wires": [ + [ + "2aaf7c7f0f0c146f" + ] + ] + }, + { + "id": "2aaf7c7f0f0c146f", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "group_stack_photos", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('group_stack_photos'):\n save('group_stack_photos', state)\n", + "outputs": 1, + "x": 660, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "84a1d063a2a2b018", + "type": "comment", + "z": "e43a27722b508115", + "name": "Messaging", + "info": "", + "x": 100, + "y": 3500, + "wires": [] + }, + { + "id": "a12ead9ccf239c19", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'telegram_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3560, + "wires": [ + [ + "d0a1a4947a1137ca" + ] + ] + }, + { + "id": "9a4c3cbe89994626", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "telegram_enable", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", + "outputs": 1, + "x": 520, + "y": 3560, + "wires": [ + [] + ] + }, + { + "id": "d0a1a4947a1137ca", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "telegram_enable", + "label": "Enable Telegram", + "tooltip": "Enable telegram bot", + "group": "220493325bb79987", + "order": 1, + "width": "6", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 340, + "y": 3560, + "wires": [ + [ + "9a4c3cbe89994626" + ] + ] + }, + { + "id": "28eeaa3a8eb77679", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "label": "Telegram Api Token", + "tooltip": "telegram api token", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "password", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3600, + "wires": [ + [ + "1c08a329bd2a669c" + ] + ] + }, + { + "id": "bf8e971a52cddab1", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3600, + "wires": [ + [ + "28eeaa3a8eb77679" + ] + ] + }, + { + "id": "1c08a329bd2a669c", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_api_token", + "func": "var file = 'telegram_api_token'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3600, + "wires": [ + [] + ] + }, + { + "id": "a26c0482377667c9", + "type": "ui_text_input", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "label": "Telegram Client Id", + "tooltip": "The Id of the user or channel to send the message to", + "group": "220493325bb79987", + "order": 5, + "width": 6, + "height": 1, + "passthru": false, + "mode": "text", + "delay": "0", + "topic": "topic", + "sendOnBlur": true, + "className": "", + "topicType": "msg", + "x": 350, + "y": 3640, + "wires": [ + [ + "b5aba11033c5f952" + ] + ] + }, + { + "id": "058743d0e5afb87b", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 190, + "y": 3640, + "wires": [ + [ + "a26c0482377667c9" + ] + ] + }, + { + "id": "b5aba11033c5f952", + "type": "function", + "z": "e43a27722b508115", + "name": "telegram_client_id", + "func": "var file = 'telegram_client_id'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 3640, + "wires": [ + [] + ] + }, + { + "id": "c59e7b205d80fe0a", + "type": "ui_button", + "z": "e43a27722b508115", + "name": "Messaging", + "group": "220493325bb79987", + "order": 1, + "width": 0, + "height": 0, + "passthru": false, + "label": "", + "tooltip": "", + "color": "", + "bgcolor": "transparent", + "className": "", + "icon": "fa-question-circle", + "payload": "

Messaging

Telegram Messaging

This adds the capability to send OpenScan status messages to Telegram. Please refer to the appropiate documentation in order to configure it

", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 770, + "y": 300, + "wires": [ + [ + "5fff689f9f8bc1ca" + ] + ] + }, + { + "id": "2afb6a45c73fa244", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 2", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3600, + "wires": [ + [ + "a12ead9ccf239c19", + "bf8e971a52cddab1", + "058743d0e5afb87b" + ] + ] + }, + { + "id": "4c7fa5b5b27b83a5", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "create beta new", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "outputs": 1, + "x": 260, + "y": 140, + "wires": [ + [ + "e23c514008cad1a1" + ] + ] + }, + { + "id": "80175eb8dc6ad009", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 100, + "y": 140, + "wires": [ + [ + "4c7fa5b5b27b83a5" + ] + ] + }, + { + "id": "d7362e6e0ec7bdaa", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "overwrite", + "v": "true", + "vt": "bool" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 90, + "y": 220, + "wires": [ + [ + "4ce127c61c3c5966", + "beacc3dc5398fa79" + ] + ] + }, + { + "id": "4ce127c61c3c5966", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "prepare image creation", + "func": "import os\n\n#factory reset, reset wpa, create wpa in boot, rm files\n#should be done before creating a new raspbian image\n\nbasepath = '/home/pi/OpenScan/'\n\n#remove files\n\ndir = basepath + 'scans/'\n\nfor i in ['scans/','tmp/']:\n os.system('rm -r ' + basepath + i)\n os.mkdir(basepath + i)\n\n#delete wifi\ntemp_dir = '/home/pi/OpenScan/tmp/wpa_empty.log'\nwpa_dir = '/etc/wpa_supplicant/wpa_supplicant.conf'\n\nwith open(temp_dir, 'w+') as file:\n file.write('update_config=1\\nctrl_interface=DIR=/var/run/wpa_supplicant\\ncountry=de\\n\\n')\nos.system('mv '+ temp_dir + ' ' + wpa_dir)\nos.system('wpa_cli -i wlan0 reconfigure')\n\n#create new wpa_supplicant.conf\nwith open('/boot/wpa_supplicant.conf','w+') as file:\n file.write('country=de\\nupdate_config=1\\nctrl_interface=/var/run/wpa_supplicant\\n\\nnetwork={\\n scan_ssid=1\\n ssid=\"wlan name\"\\n psk=\"xxxx\"\\n}')\nos.system(\"chmod a+rwx /boot/wpa_supplicant.conf\")\n\n\n#rm tmp dir\n\n\n#stop photos:\nos.system('systemctl stop flask')\nos.system('rm -r ' + basepath + 'tmp')\nos.system('mkdir ' + basepath + 'tmp')\n\nos.system('systemctl stop nodered')\n\n#reset factory\n\n", + "outputs": 1, + "x": 290, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "beacc3dc5398fa79", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "38783aea9cc317a6" + ], + "x": 195, + "y": 260, + "wires": [] + }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, + { + "id": "b0629875a30ae1d7", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "outputs": 2, + "x": 390, + "y": 540, + "wires": [ + [ + "1bbe2d769f42c313" + ], + [ + "fefe45404bdb19c4" + ] + ] + }, + { + "id": "c7b6d05a62172432", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "Status:", + "format": "{{msg.status}}", + "layout": "row-spread", + "className": "", + "x": 210, + "y": 400, + "wires": [] + }, + { + "id": "fefe45404bdb19c4", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "check files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "outputs": 1, + "x": 550, + "y": 560, + "wires": [ + [ + "1bbe2d769f42c313", + "ae92a328af306ebb" + ] + ] + }, + { + "id": "d0104e0163745993", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 115, + "y": 440, + "wires": [ + [ + "ec30638407332e43", + "38cbf7965d1c1834", + "49f1ecb29a3f84f4" + ] + ] + }, + { + "id": "ec30638407332e43", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadS", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 480, + "wires": [ + [ + "2852023f3aa8db10" + ] + ] + }, + { + "id": "2852023f3aa8db10", + "type": "ui_dropdown", + "z": "a5557543ccff5889", + "name": "", + "label": "", + "tooltip": "", + "place": "Select option", + "group": "ddbd496e.93a288", + "order": 5, + "width": 2, + "height": 1, + "passthru": false, + "multiple": false, + "options": [ + { + "label": "stable", + "value": "stable", + "type": "str" + }, + { + "label": "beta", + "value": "beta", + "type": "str" + }, + { + "label": "meanwhile", + "value": "meanwhile", + "type": "str" + } + ], + "payload": "", + "topic": "topic", + "topicType": "msg", + "className": "", + "x": 340, + "y": 480, + "wires": [ + [ + "1e10b387ee30c486" + ] + ] + }, + { + "id": "1e10b387ee30c486", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "274129c51b0b87ef", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "order": 4, + "width": 4, + "height": 1, + "name": "", + "label": "Updatetype: ", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 610, + "y": 480, + "wires": [] + }, + { + "id": "51cd8c8643e6b46a", + "type": "ui_switch", + "z": "a5557543ccff5889", + "name": "", + "label": "Auto-check update availability", + "tooltip": "", + "group": "ddbd496e.93a288", + "order": 6, + "width": 6, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "", + "topicType": "str", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 410, + "y": 440, + "wires": [ + [ + "1ab4c6b4b232a022" + ] + ] + }, + { + "id": "38cbf7965d1c1834", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 440, + "wires": [ + [ + "51cd8c8643e6b46a" + ] + ] + }, + { + "id": "1ab4c6b4b232a022", + "type": "function", + "z": "a5557543ccff5889", + "name": "write", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 610, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "ae92a328af306ebb", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "NO", + "cancel": "YES", + "raw": false, + "className": "", + "topic": "", + "name": "", + "x": 710, + "y": 560, + "wires": [ + [ + "2de63e8e3ae5fb0c", + "929281fef53e09f8" + ] + ] + }, + { + "id": "cbd0afc4aa7b302a", + "type": "link in", + "z": "a5557543ccff5889", + "name": "update status", + "links": [ + "1bbe2d769f42c313", + "42061b28cff81f99" + ], + "x": 115, + "y": 400, + "wires": [ + [ + "c7b6d05a62172432", + "c94623ddd9d95f78" + ] + ] + }, + { + "id": "1bbe2d769f42c313", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 665, + "y": 520, + "wires": [] + }, + { + "id": "7cf60615d93e696b", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 7, + "width": 6, + "height": 1, + "passthru": false, + "label": "Check Updates", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 180, + "y": 560, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "2de63e8e3ae5fb0c", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "download files", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "outputs": 1, + "x": 880, + "y": 560, + "wires": [ + [ + "42061b28cff81f99", + "fe3a855fee9e28c6" + ] + ] + }, + { + "id": "929281fef53e09f8", + "type": "function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "if (msg.payload == 'YES'){\n msg.status = 'Installing updates'\n return msg}", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 520, + "wires": [ + [ + "42061b28cff81f99" + ] + ] + }, + { + "id": "42061b28cff81f99", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "cbd0afc4aa7b302a" + ], + "x": 995, + "y": 520, + "wires": [] + }, + { + "id": "49f1ecb29a3f84f4", + "type": "function", + "z": "a5557543ccff5889", + "name": "loadB", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 210, + "y": 520, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "fe3a855fee9e28c6", + "type": "link out", + "z": "a5557543ccff5889", + "name": "", + "mode": "link", + "links": [ + "9bb0adbd716ce347", + "01c882fcc51b349c" + ], + "x": 995, + "y": 560, + "wires": [] + }, + { + "id": "5e7d5e4335d37794", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "960912e90ba5b5bc", + "50eeb3e362f9027f" + ], + "x": 95, + "y": 700, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "2bb5fe78e09fec8a", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "msg", + "func": "\nfrom subprocess import getoutput\nimport os\n\nmsg['os'] = getoutput(\"cat /etc/os-release | grep -i 'PRETTY_NAME'\")[13:-1]\nmsg['device'] = getoutput(\"cat /proc/device-tree/model\")\nmsg['flask'] = getoutput(\"systemctl status flask |grep -i 'Active:'\").split(' ')[6]\nmsg['osdate'] = getoutput(\"vcgencmd version\").split('\\n')[0]\nmsg['temp'] = getoutput(\"vcgencmd measure_temp\").split('=')[1]\ncpu_total = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $2}'\")\ncpu_used = getoutput(\"free -m | head -n2 | tail -n1 |awk '{print $3}'\")\nswap_total = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $2}'\")\nswap_used = getoutput(\"free -m | head -n3 | tail -n1 |awk '{print $3}'\")\ndiskspace_used = getoutput(\"df -h / | tail -n1 |awk '{print $3}'\")\ndiskspace_total = getoutput(\"df -h / | tail -n1 |awk '{print $2}'\")\n\nmsg['cpu'] = cpu_used + '/' + cpu_total + 'MB'\nmsg['swap'] = swap_used + '/' + swap_total + 'MB'\nmsg['diskspace'] =diskspace_used + '/' + diskspace_total\n\nif msg['flask'] == 'inactive':\n os.system('systemctl restart flask')\n\nreturn msg", + "outputs": 1, + "x": 210, + "y": 700, + "wires": [ + [ + "dbc77052ac950624", + "d97c3068ef5fef96", + "73a3b828f862312b", + "901e31453b2bdff8", + "f983854748ee4763", + "5347c7c517f5e8c7", + "3a5016f7003cd72c", + "6d720c4a4ecd9475", + "6438b7d060a70d81" + ] + ] + }, + { + "id": "d97c3068ef5fef96", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 2, + "width": 0, + "height": 0, + "name": "", + "label": "OS:", + "format": "{{msg.os}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 740, + "wires": [] + }, + { + "id": "73a3b828f862312b", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 8, + "width": 0, + "height": 0, + "name": "", + "label": "Flask:", + "format": "{{msg.flask}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 780, + "wires": [] + }, + { + "id": "dbc77052ac950624", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 1, + "width": 0, + "height": 0, + "name": "", + "label": "Device:", + "format": "{{msg.device}}", + "layout": "row-spread", + "className": "", + "x": 500, + "y": 700, + "wires": [] + }, + { + "id": "3f42560297fe6978", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "name": "Download LOG", + "order": 9, + "width": 6, + "height": 1, + "format": "\n
Download error log\n
\n", + "storeOutMessages": false, + "fwdInMessages": false, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 180, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c94623ddd9d95f78", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "get update", + "func": "from OpenScan import save\n\nif msg['status'] == \"No new update available\":\n save('updateable',False)\nelif msg['status'] == \"New update available\":\n save('updateable',True)\n", + "outputs": 1, + "x": 210, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "39a502b38837273d", + "type": "link in", + "z": "a5557543ccff5889", + "name": "", + "links": [ + "1e7457ea9c2c5e09" + ], + "x": 245, + "y": 600, + "wires": [ + [ + "b0629875a30ae1d7" + ] + ] + }, + { + "id": "901e31453b2bdff8", + "type": "delay", + "z": "a5557543ccff5889", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 220, + "y": 740, + "wires": [ + [ + "2bb5fe78e09fec8a" + ] + ] + }, + { + "id": "f983854748ee4763", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 3, + "width": 0, + "height": 0, + "name": "", + "label": "", + "format": "{{msg.osdate}}", + "layout": "row-spread", + "className": "", + "x": 490, + "y": 820, + "wires": [] + }, + { + "id": "5347c7c517f5e8c7", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 4, + "width": 0, + "height": 0, + "name": "", + "label": "CPU temp:", + "format": "{{msg.temp}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 860, + "wires": [] + }, + { + "id": "3a5016f7003cd72c", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 5, + "width": 0, + "height": 0, + "name": "", + "label": "CPU memory:", + "format": "{{msg.cpu}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 900, + "wires": [] + }, + { + "id": "6d720c4a4ecd9475", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 6, + "width": 0, + "height": 0, + "name": "", + "label": "Swap memory:", + "format": "{{msg.swap}}", + "layout": "row-spread", + "className": "", + "x": 520, + "y": 940, + "wires": [] + }, + { + "id": "6438b7d060a70d81", + "type": "ui_text", + "z": "a5557543ccff5889", + "group": "3ce32450.e0cffc", + "order": 7, + "width": 0, + "height": 0, + "name": "", + "label": "Diskspace:", + "format": "{{msg.diskspace}}", + "layout": "row-spread", + "className": "", + "x": 510, + "y": 980, + "wires": [] + }, + { + "id": "8d012912f302be85", + "type": "ui_button", + "z": "a5557543ccff5889", + "name": "", + "group": "ddbd496e.93a288", + "order": 8, + "width": 6, + "height": 1, + "passthru": false, + "label": "Show Details/Changelog", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 210, + "y": 640, + "wires": [ + [ + "5242607a723cc628" + ] + ] + }, + { + "id": "5242607a723cc628", + "type": "python3-function", + "z": "a5557543ccff5889", + "name": "Changelog", + "func": "import requests\n\ntempfile = '/home/pi/OpenScan/tmp/changelog'\n\nurl = 'https://raw.githubusercontent.com/stealthizer/Openscan2/main/docs/changelog.md'\nr = requests.get(url, allow_redirects=False)\n\nwith open(tempfile,'wb') as file:\n file.write(r.content)\n \nwith open(tempfile, 'r') as file:\n text = file.read()\n \ntext = text.replace('\\n','
').replace('*', '  - ')\nmsg['payload'] = text\n\nreturn msg", + "outputs": 1, + "x": 430, + "y": 640, + "wires": [ + [ + "573722197b15bf84" + ] + ] + }, + { + "id": "573722197b15bf84", + "type": "ui_toast", + "z": "a5557543ccff5889", + "position": "dialog", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 1, + "ok": "OK", + "cancel": "", + "raw": true, + "className": "", + "topic": "", + "name": "", + "x": 610, + "y": 640, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/update/2024-12o/stable/settings.js b/update/2024-12o/stable/settings.js new file mode 100644 index 0000000..357b02b --- /dev/null +++ b/update/2024-12o/stable/settings.js @@ -0,0 +1,512 @@ +/** + * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT + * + * It can contain any valid JavaScript code that will get run when Node-RED + * is started. + * + * Lines that start with // are commented out. + * Each entry should be separated from the entries above and below by a comma ',' + * + * For more information about individual settings, refer to the documentation: + * https://nodered.org/docs/user-guide/runtime/configuration + * + * The settings are split into the following sections: + * - Flow File and User Directory Settings + * - Security + * - Server Settings + * - Runtime Settings + * - Editor Settings + * - Node Settings + * + **/ +process.env.HOSTNAME = require('os').hostname(); + +module.exports = { + +/******************************************************************************* + * Flow File and User Directory Settings + * - flowFile + * - credentialSecret + * - flowFilePretty + * - userDir + * - nodesDir + ******************************************************************************/ + + /** The file containing the flows. If not set, defaults to flows_.json **/ + flowFile: "flows.json", + + /** By default, credentials are encrypted in storage using a generated key. To + * specify your own secret, set the following property. + * If you want to disable encryption of credentials, set this property to false. + * Note: once you set this property, do not change it - doing so will prevent + * node-red from being able to decrypt your existing credentials and they will be + * lost. + */ + credentialSecret: false, + + /** By default, the flow JSON will be formatted over multiple lines making + * it easier to compare changes when using version control. + * To disable pretty-printing of the JSON set the following property to false. + */ + flowFilePretty: true, + + /** By default, all user data is stored in a directory called `.node-red` under + * the user's home directory. To use a different location, the following + * property can be used + */ + //userDir: '/home/nol/.node-red/', +userDir: '/home/pi/OpenScan/settings/.node-red/', + + /** Node-RED scans the `nodes` directory in the userDir to find local node files. + * The following property can be used to specify an additional directory to scan. + */ + //nodesDir: '/home/nol/.node-red/nodes', + +/******************************************************************************* + * Security + * - adminAuth + * - https + * - httpsRefreshInterval + * - requireHttps + * - httpNodeAuth + * - httpStaticAuth + ******************************************************************************/ + + /** To password protect the Node-RED editor and admin API, the following + * property can be used. See http://nodered.org/docs/security.html for details. + */ + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + /** The following property can be used to enable HTTPS + * This property can be either an object, containing both a (private) key + * and a (public) certificate, or a function that returns such an object. + * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + * for details of its contents. + */ + + /** Option 1: static object */ + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + + /** Option 2: function that returns the HTTP configuration object */ + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + /** If the `https` setting is a function, the following setting can be used + * to set how often, in hours, the function will be called. That can be used + * to refresh any certificates. + */ + //httpsRefreshInterval : 12, + + /** The following property can be used to cause insecure HTTP connections to + * be redirected to HTTPS. + */ + //requireHttps: true, + + /** To password protect the node-defined HTTP endpoints (httpNodeRoot), + * including node-red-dashboard, or the static content (httpStatic), the + * following properties can be used. + * The `pass` field is a bcrypt hash of the password. + * See http://nodered.org/docs/security.html#generating-the-password-hash + */ + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + +/******************************************************************************* + * Server Settings + * - uiPort + * - uiHost + * - apiMaxLength + * - httpServerOptions + * - httpAdminRoot + * - httpAdminMiddleware + * - httpNodeRoot + * - httpNodeCors + * - httpNodeMiddleware + * - httpStatic + * - httpStaticRoot + ******************************************************************************/ + + /** the tcp port that the Node-RED web server is listening on */ + uiPort: process.env.PORT || 80, + + /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. + * To listen on all IPv6 addresses, set uiHost to "::", + * The following property can be used to listen on a specific interface. For + * example, the following would only allow connections from the local machine. + */ + //uiHost: "127.0.0.1", + + /** The maximum size of HTTP request that will be accepted by the runtime api. + * Default: 5mb + */ + //apiMaxLength: '5mb', + + /** The following property can be used to pass custom options to the Express.js + * server used by Node-RED. For a full list of available options, refer + * to http://expressjs.com/en/api.html#app.settings.table + */ + //httpServerOptions: { }, + + /** By default, the Node-RED UI is available at http://localhost:1880/ + * The following property can be used to specify a different root path. + * If set to false, this is disabled. + */ + httpAdminRoot: '/editor', + + /** The following property can be used to add a custom middleware function + * in front of all admin http routes. For example, to set custom http + * headers. It can be a single function or an array of middleware functions. + */ + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + + /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. + * By default, these are served relative to '/'. The following property + * can be used to specifiy a different root path. If set to false, this is + * disabled. + */ + //httpNodeRoot: '/red-nodes', + + /** The following property can be used to configure cross-origin resource sharing + * in the HTTP nodes. + * See https://github.com/troygoode/node-cors#configuration-options for + * details on its contents. The following is a basic permissive set of options: + */ + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + /** If you need to set an http proxy please set an environment variable + * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + * For example - http_proxy=http://myproxy.com:8080 + * (Setting it here will have no effect) + * You may also specify no_proxy (or NO_PROXY) to supply a comma separated + * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + */ + + /** The following property can be used to add a custom middleware function + * in front of all http in nodes. This allows custom authentication to be + * applied to all http in nodes, or any other sort of common request processing. + * It can be a single function or an array of middleware functions. + */ + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + /** When httpAdminRoot is used to move the UI to a different root path, the + * following property can be used to identify a directory of static content + * that should be served at http://localhost:1880/. + * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * to move httpAdminRoot + */ + httpStatic: '/home/pi/OpenScan/', + + //httpStatic: '/home/nol/node-red-static/', //single static source + /* OR multiple static sources can be created using an array of objects... */ + //httpStatic: [ + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, + //], + + /** + * All static routes will be appended to httpStaticRoot + * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" + * then "/home/nol/docs" will be served at "/static/" + * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] + * and httpStaticRoot = "/static/" + * then "/home/nol/pics/" will be served at "/static/img/" + */ + //httpStaticRoot: '/static/', + +/******************************************************************************* + * Runtime Settings + * - lang + * - logging + * - contextStorage + * - exportGlobalContextKeys + * - externalModules + ******************************************************************************/ + + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", + + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, + + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, + + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: [], + // denyList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, + + +/******************************************************************************* + * Editor Settings + * - disableEditor + * - editorTheme + ******************************************************************************/ + + /** The following property can be used to disable the editor. The admin API + * is not affected by this option. To disable both the editor and the admin + * API, use either the httpRoot or httpAdminRoot properties + */ + //disableEditor: false, + + /** Customising the editor + * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes + * for all available options. + */ + editorTheme: { + /** The following property can be used to set a custom theme for the editor. + * See https://github.com/node-red-contrib-themes/theme-collection for + * a collection of themes to chose from. + */ + //theme: "", + palette: { + /** The following property can be used to order the categories in the editor + * palette. If a node's category is not in the list, the category will get + * added to the end of the palette. + * If not set, the following default order is used: + */ + //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + }, + projects: { + /** To enable the Projects feature, set this value to true */ + enabled: false, + workflow: { + /** Set the default projects workflow mode. + * - manual - you must manually commit changes + * - auto - changes are automatically committed + * This can be overridden per-user from the 'Git config' + * section of 'User Settings' within the editor + */ + mode: "manual" + } + }, + codeEditor: { + /** Select the text editor component used by the editor. + * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired + */ + lib: "monaco", + options: { + /** The follow options only apply if the editor is set to "monaco" + * + * theme - must match the file name of a theme in + * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme + * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" + */ + theme: "vs", + /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. + * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html + */ + //fontSize: 14, + //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", + //fontLigatures: true, + } + } + }, + +/******************************************************************************* + * Node Settings + * - fileWorkingDirectory + * - functionGlobalContext + * - functionExternalModules + * - nodeMessageBufferMaxLength + * - ui (for use with Node-RED Dashboard) + * - debugUseColors + * - debugMaxLength + * - execMaxBufferSize + * - httpRequestTimeout + * - mqttReconnectTime + * - serialReconnectTime + * - socketReconnectTime + * - socketTimeout + * - tcpMsgQueueSize + * - inboundWebSocketTimeout + * - tlsConfigDisableLocalFiles + * - webSocketNodeVerifyClient + ******************************************************************************/ + + /** The working directory to handle relative file paths from within the File nodes + * defaults to the working directory of the Node-RED process. + */ + //fileWorkingDirectory: "", + + /** Allow the Function node to load additional npm modules directly */ + functionExternalModules: true, + + /** The following property can be used to set predefined values in Global Context. + * This allows extra node modules to be made available with in Function node. + * For example, the following: + * functionGlobalContext: { os:require('os') } + * will allow the `os` module to be accessed in a Function node using: + * global.get("os") + */ +// functionGlobalContext: { + // os:require('os'), + // }, +functionGlobalContext: { // enables and pre-populates the context.global variable + os:require('os'), + path:require('path'), + fs:require('fs') + }, + /** The maximum number of messages nodes will buffer internally as part of their + * operation. This applies across a range of nodes that operate on message sequences. + * defaults to no limit. A value of 0 also means no limit is applied. + */ + //nodeMessageBufferMaxLength: 0, + + /** If you installed the optional node-red-dashboard you can set it's path + * relative to httpNodeRoot + * Other optional properties include + * readOnly:{boolean}, + * middleware:{function or array}, (req,res,next) - http middleware + * ioMiddleware:{function or array}, (socket,next) - socket.io middleware + */ + ui: { path: "" }, + + /** Colourise the console output of the debug node */ + //debugUseColors: true, + + /** The maximum length, in characters, of any message sent to the debug sidebar tab */ + debugMaxLength: 1000, + + /** Maximum buffer size for the exec node. Defaults to 10Mb */ + //execMaxBufferSize: 10000000, + + /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ + //httpRequestTimeout: 120000, + + /** Retry time in milliseconds for MQTT connections */ + mqttReconnectTime: 15000, + + /** Retry time in milliseconds for Serial port connections */ + serialReconnectTime: 15000, + + /** Retry time in milliseconds for TCP socket connections */ + //socketReconnectTime: 10000, + + /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ + //socketTimeout: 120000, + + /** Maximum number of messages to wait in queue while attempting to connect to TCP socket + * defaults to 1000 + */ + //tcpMsgQueueSize: 2000, + + /** Timeout in milliseconds for inbound WebSocket connections that do not + * match any configured node. Defaults to 5000 + */ + //inboundWebSocketTimeout: 5000, + + /** To disable the option for using local files for storing keys and + * certificates in the TLS configuration node, set this to true. + */ + //tlsConfigDisableLocalFiles: true, + + /** The following property can be used to verify websocket connection attempts. + * This allows, for example, the HTTP request headers to be checked to ensure + * they include valid authentication information. + */ + //webSocketNodeVerifyClient: function(info) { + // /** 'info' has three properties: + // * - origin : the value in the Origin header + // * - req : the HTTP request + // * - secure : true if req.connection.authorized or req.connection.encrypted is set + // * + // * The function should return true if the connection should be accepted, false otherwise. + // * + // * Alternatively, if this function is defined to accept a second argument, callback, + // * it can be used to verify the client asynchronously. + // * The callback takes three arguments: + // * - result : boolean, whether to accept the connection or not + // * - code : if result is false, the HTTP error status to return + // * - reason: if result is false, the HTTP reason string to return + // */ + //}, +} diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json new file mode 100644 index 0000000..9df09ab --- /dev/null +++ b/update/2024-12o/update.json @@ -0,0 +1,114 @@ +{ + "stable": { + "1": { + "src": "meanwhile/OpenScan.py", + "dst": "/usr/lib/python3/dist-packages/OpenScan.py", + "filesize": 10249 + }, + "2": { + "src": "meanwhile/OpenScanStatistics.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", + "filesize": 793 + }, + "3": { + "src": "meanwhile/config.txt", + "dst": "/boot/config.txt", + "filesize": 864 + }, + "4": { + "src": "meanwhile/expand_root.sh", + "dst": "/home/pi/OpensScan/files/expand_root.sh", + "filesize": 170 + }, + "5": { + "src": "meanwhile/fla.py", + "dst": "/home/pi/OpenScan/files/fla.py", + "filesize": 17869 + }, + "6": { + "src": "meanwhile/flows.json", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", + "filesize": 327660 + }, + "7": { + "src": "meanwhile/settings.js", + "dst": "/root/.node-red/settings.js", + "filesize": 21248 + } + }, + + "beta": { + "1": { + "src": "meanwhile/OpenScan.py", + "dst": "/usr/lib/python3/dist-packages/OpenScan.py", + "filesize": 10249 + }, + "2": { + "src": "meanwhile/OpenScanStatistics.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", + "filesize": 793 + }, + "3": { + "src": "meanwhile/config.txt", + "dst": "/boot/config.txt", + "filesize": 864 + }, + "4": { + "src": "meanwhile/expand_root.sh", + "dst": "/home/pi/OpensScan/files/expand_root.sh", + "filesize": 170 + }, + "5": { + "src": "meanwhile/fla.py", + "dst": "/home/pi/OpenScan/files/fla.py", + "filesize": 17869 + }, + "6": { + "src": "meanwhile/flows.json", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", + "filesize": 333640 + }, + "7": { + "src": "meanwhile/settings.js", + "dst": "/root/.node-red/settings.js", + "filesize": 21248 + } + }, + "meanwhile": { + "1": { + "src": "meanwhile/OpenScan.py", + "dst": "/usr/lib/python3/dist-packages/OpenScan.py", + "filesize": 10249 + }, + "2": { + "src": "meanwhile/OpenScanStatistics.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", + "filesize": 793 + }, + "3": { + "src": "meanwhile/config.txt", + "dst": "/boot/config.txt", + "filesize": 864 + }, + "4": { + "src": "meanwhile/expand_root.sh", + "dst": "/home/pi/OpensScan/files/expand_root.sh", + "filesize": 170 + }, + "5": { + "src": "meanwhile/fla.py", + "dst": "/home/pi/OpenScan/files/fla.py", + "filesize": 17869 + }, + "6": { + "src": "meanwhile/flows.json", + "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", + "filesize": 341591 + }, + "7": { + "src": "meanwhile/settings.js", + "dst": "/root/.node-red/settings.js", + "filesize": 21248 + } + } +} \ No newline at end of file From d330df6dcf6793ec23adaa405724002a71f99790 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 29 Sep 2024 20:32:18 +0200 Subject: [PATCH 18/38] 2024-12o stable --- update/2024-12o/stable/flows.json | 694 +++++++++++++++++++++++------- update/2024-12o/update.json | 2 +- 2 files changed, 548 insertions(+), 148 deletions(-) diff --git a/update/2024-12o/stable/flows.json b/update/2024-12o/stable/flows.json index 3f68979..9ba0c97 100644 --- a/update/2024-12o/stable/flows.json +++ b/update/2024-12o/stable/flows.json @@ -39,6 +39,14 @@ "info": "", "env": [] }, + { + "id": "87715429b0b1c9a3", + "type": "tab", + "label": "Statistics", + "disabled": false, + "info": "", + "env": [] + }, { "id": "90223f7ddc082321", "type": "ui_group", @@ -165,7 +173,7 @@ "link": "https://openscan-org.github.io/OpenScan-Doc/", "icon": "fa-bookmark", "target": "iframe", - "order": 6 + "order": 8 }, { "id": "23f75a8768250ce8", @@ -174,7 +182,7 @@ "link": "https://www.patreon.com/OpenScan", "icon": "fa-bookmark", "target": "newtab", - "order": 5 + "order": 7 }, { "id": "b5fdd57b.15eda8", @@ -304,7 +312,7 @@ "type": "ui_group", "name": "Network", "tab": "457102eadc9ddb6c", - "order": 2, + "order": 4, "disp": true, "width": "6", "collapse": true, @@ -315,7 +323,7 @@ "type": "ui_group", "name": "Pinout", "tab": "457102eadc9ddb6c", - "order": 6, + "order": 3, "disp": true, "width": "6", "collapse": true, @@ -326,7 +334,7 @@ "type": "ui_group", "name": "Motor", "tab": "457102eadc9ddb6c", - "order": 5, + "order": 7, "disp": true, "width": "6", "collapse": true, @@ -337,7 +345,7 @@ "type": "ui_group", "name": "Camera", "tab": "457102eadc9ddb6c", - "order": 4, + "order": 6, "disp": true, "width": "6", "collapse": true, @@ -348,7 +356,7 @@ "type": "ui_group", "name": "OpenScanCloud", "tab": "457102eadc9ddb6c", - "order": 3, + "order": 5, "disp": true, "width": "6", "collapse": false, @@ -359,7 +367,7 @@ "type": "ui_tab", "name": "Settings", "icon": "dashboard", - "order": 4, + "order": 5, "disabled": false, "hidden": false }, @@ -478,12 +486,74 @@ "type": "ui_group", "name": "Messaging", "tab": "457102eadc9ddb6c", - "order": 7, + "order": 8, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "ac59b8fb186de073", + "type": "ui_group", + "name": "Statistics", + "tab": "656b4eb8b15dab8f", + "order": 3, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "656b4eb8b15dab8f", + "type": "ui_tab", + "name": "Statistics", + "icon": "dashboard", + "order": 6, + "disabled": false, + "hidden": false + }, + { + "id": "0b244f698c7ac9a2", + "type": "ui_group", + "name": "Shield Type", + "tab": "457102eadc9ddb6c", + "order": 2, "disp": true, "width": "6", "collapse": false, "className": "" }, + { + "id": "e357ef02.ef3cb", + "type": "ui_group", + "name": "Page 1", + "tab": "4bc17c6e.b74934", + "order": 2, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "c9165343995892c6", + "type": "ui_group", + "name": "Page 2", + "tab": "", + "order": 1, + "disp": false, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "4bc17c6e.b74934", + "type": "ui_tab", + "name": "Page 1", + "icon": "wi-wu-tstorms", + "order": 5, + "disabled": false, + "hidden": false + }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -506,7 +576,7 @@ "payload": "", "payloadType": "date", "x": 100, - "y": 460, + "y": 520, "wires": [ [ "949bafced17d66d6" @@ -525,7 +595,7 @@ "finalize": "", "libs": [], "x": 250, - "y": 460, + "y": 520, "wires": [ [] ] @@ -561,7 +631,8 @@ "b1e2491c952f84c9", "fac6626127bba4f5", "bc2f0adaf72f97e9", - "ac242724fe7605a6" + "ac242724fe7605a6", + "d81572486f15cd7a" ] ] }, @@ -636,7 +707,9 @@ "cb40b9341bd22a28", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244" + "2afb6a45c73fa244", + "9b3e6a06c82a0f52", + "fbc5fc2e65311f8b" ], "x": 645, "y": 60, @@ -677,7 +750,7 @@ "payload": "", "payloadType": "str", "x": 100, - "y": 380, + "y": 440, "wires": [ [ "6c6ef2255a7d39e5" @@ -696,7 +769,7 @@ "6bf8344af427a6ba" ], "x": 205, - "y": 380, + "y": 440, "wires": [] }, { @@ -757,9 +830,9 @@ "name": "enable", "mode": "link", "links": [ + "65518f3d4e3095e5", "8367cfa0bf5bc5df", - "c8b93b42c720b9cf", - "65518f3d4e3095e5" + "c8b93b42c720b9cf" ], "x": 345, "y": 280, @@ -836,8 +909,8 @@ "resendOnRefresh": true, "templateScope": "global", "className": "", - "x": 580, - "y": 140, + "x": 650, + "y": 360, "wires": [ [] ] @@ -876,7 +949,7 @@ "topic": "", "topicType": "str", "x": 90, - "y": 540, + "y": 600, "wires": [ [ "62cd5288.2805fc" @@ -904,7 +977,7 @@ "topic": "", "topicType": "str", "x": 100, - "y": 620, + "y": 680, "wires": [ [ "62cd5288.2805fc" @@ -918,7 +991,7 @@ "name": "", "events": "all", "x": 280, - "y": 540, + "y": 600, "wires": [ [] ] @@ -944,7 +1017,7 @@ "topic": "", "topicType": "str", "x": 90, - "y": 580, + "y": 640, "wires": [ [ "62cd5288.2805fc" @@ -972,7 +1045,7 @@ "topic": "", "topicType": "str", "x": 110, - "y": 660, + "y": 720, "wires": [ [ "62cd5288.2805fc" @@ -1000,7 +1073,7 @@ "topic": "", "topicType": "str", "x": 120, - "y": 720, + "y": 820, "wires": [ [ "1e7457ea9c2c5e09" @@ -1017,7 +1090,7 @@ "39a502b38837273d" ], "x": 245, - "y": 720, + "y": 820, "wires": [] }, { @@ -1090,34 +1163,23 @@ "name": "started1s", "mode": "link", "links": [ + "1dffb799fdf10cbc", + "2afb6a45c73fa244", + "2e9b29c70969cf01", "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "b4c843620c251c43", "3876d5cbd248592b", - "a4c81754c148b86f", - "2e9b29c70969cf01", - "2477f81cddc8fa31", - "29036b35dfd672c6", "592ec13d8f8923a9", + "5e7d5e4335d37794", + "9b3e6a06c82a0f52", + "9fd259de91de1da1", + "b4c843620c251c43", "cb40b9341bd22a28", + "d0104e0163745993", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244" + "e5f38b4a07a5e278", + "fbc5fc2e65311f8b", + "fd0258418489839d" ], "x": 1015, "y": 540, @@ -1186,6 +1248,98 @@ [] ] }, + { + "id": "e548168473aa85d6", + "type": "ui_button", + "z": "e6f4d02efb300ea9", + "name": "", + "group": "729f9ea6e3513c9b", + "order": 5, + "width": 0, + "height": 0, + "passthru": false, + "label": "Statistics", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "5", + "payloadType": "str", + "topic": "topic", + "topicType": "msg", + "x": 100, + "y": 760, + "wires": [ + [ + "62cd5288.2805fc" + ] + ] + }, + { + "id": "d81572486f15cd7a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "loadl", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = String(data);\nflow.set('openscan_version',data)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 360, + "wires": [ + [ + "b1b0ccb783dd5882" + ] + ] + }, + { + "id": "fa6db57803ae2b6d", + "type": "debug", + "z": "e6f4d02efb300ea9", + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": true, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 590, + "y": 460, + "wires": [] + }, + { + "id": "b1b0ccb783dd5882", + "type": "change", + "z": "e6f4d02efb300ea9", + "name": "openscan_version", + "rules": [ + { + "t": "set", + "p": "openscan_version", + "pt": "flow", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 415, + "y": 360, + "wires": [ + [ + "fa6db57803ae2b6d", + "0d8c6bc7887fb3c2" + ] + ] + }, { "id": "6a3d9acbe097a3d2", "type": "function", @@ -2004,10 +2158,6 @@ "props": [ { "p": "payload" - }, - { - "p": "topic", - "vt": "str" } ], "repeat": "", @@ -2142,7 +2292,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\n\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\n\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\n\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n \n return\n\n # THE FOLLOWING DOES NOT WORK PROPERLY WITH THREADING ?!\n\n #tt_thread = threading.Thread(target=motorrun, args=('tt', tt_angle))\n #rotor_thread = threading.Thread(target=motorrun, args=('rotor', rotor_angle))\n #tt_thread.start()\n #rotor_thread.start()\n #tt_thread.join()\n #rotor_thread.join()\n\n\ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\nimport os\n\ntry:\n os.remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\n\nsystem('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -5854,49 +6004,6 @@ "y": 3320, "wires": [] }, - { - "id": "74e455136b5ca5dd", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "endstop2", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 21, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3360, - "wires": [ - [ - "a4a89668ce4c9f05" - ] - ] - }, - { - "id": "3a74f653800eb831", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 20, - "width": 4, - "height": 1, - "name": "endstop2", - "label": "Endstop Turntable", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3360, - "wires": [] - }, { "id": "5fcef1cb2e9e4788", "type": "ui_toast", @@ -6951,42 +7058,6 @@ [] ] }, - { - "id": "21dc963d967d9c99", - "type": "function", - "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 3360, - "wires": [ - [ - "74e455136b5ca5dd" - ] - ] - }, - { - "id": "a4a89668ce4c9f05", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3360, - "wires": [ - [] - ] - }, { "id": "22ef66b0e2058be2", "type": "function", @@ -7032,7 +7103,7 @@ "name": "loadB", "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "noerr": 0, + "noerr": 7, "initialize": "", "finalize": "", "libs": [], @@ -7355,7 +7426,8 @@ "22ef66b0e2058be2", "9ce01c8ba97932c1", "81356177176eebcf", - "d54b85891248ba88" + "d54b85891248ba88", + "53681e53353db898" ] ] }, @@ -7456,8 +7528,7 @@ "0456a9ec4c236c9e", "09d37ba08ec0f163", "37d954a4cf7e87ea", - "cc6dabe017a9c8a8", - "21dc963d967d9c99" + "cc6dabe017a9c8a8" ] ] }, @@ -7707,7 +7778,7 @@ "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('reboot -h')\n", "outputs": 1, "x": 270, - "y": 520, + "y": 560, "wires": [ [] ] @@ -7723,7 +7794,7 @@ "09d4a9c756161e10" ], "x": 155, - "y": 520, + "y": 560, "wires": [ [ "e2411b49791840e0" @@ -8221,7 +8292,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 410, + "x": 450, "y": 1980, "wires": [ [ @@ -8332,7 +8403,7 @@ "name": "telegram_enable", "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", "outputs": 1, - "x": 520, + "x": 540, "y": 3560, "wires": [ [] @@ -8543,12 +8614,235 @@ ] ] }, + { + "id": "e98c1b83744bb863", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "", + "label": "Delete Aborted", + "tooltip": "Delete aborted photosets", + "group": "d324f0b852c2df0a", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 420, + "y": 520, + "wires": [ + [ + "7438a5bf5fcddec4" + ] + ] + }, + { + "id": "7438a5bf5fcddec4", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "delete_aborted", + "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('delete_aborted'):\n save('delete_aborted', state)\n", + "outputs": 1, + "x": 600, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "53681e53353db898", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'delete_aborted'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 270, + "y": 520, + "wires": [ + [ + "e98c1b83744bb863" + ] + ] + }, + { + "id": "48386fdb54980ec7", + "type": "comment", + "z": "e43a27722b508115", + "name": "Shield", + "info": "", + "x": 90, + "y": 3760, + "wires": [] + }, + { + "id": "fbc5fc2e65311f8b", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3840, + "wires": [ + [ + "5618e266f6966ae6" + ] + ] + }, + { + "id": "5618e266f6966ae6", + "type": "function", + "z": "e43a27722b508115", + "name": "loadl", + "func": "var file = 'shield_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 170, + "y": 3840, + "wires": [ + [ + "c97113d841391e40" + ] + ] + }, + { + "id": "c97113d841391e40", + "type": "ui_dropdown", + "z": "e43a27722b508115", + "name": "", + "label": "Shield Type", + "tooltip": "", + "place": "Select option", + "group": "0b244f698c7ac9a2", + "order": 0, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "Green", + "value": "green", + "type": "str" + }, + { + "label": "Black", + "value": "black", + "type": "str" + } + ], + "payload": "", + "topic": "payload", + "topicType": "msg", + "className": "", + "x": 310, + "y": 3840, + "wires": [ + [ + "2b639346c1b56578" + ] + ] + }, + { + "id": "2b639346c1b56578", + "type": "function", + "z": "e43a27722b508115", + "name": "function 2", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'shield_type';\nconst current_shield = fs.readFileSync(filepath + file, 'utf8');\n\nvar current_choice = msg.payload\n\nif (current_choice != current_shield) {\n \n switch (current_choice) {\n case \"green\":\n fs.writeFile(filepath + 'pin_external', String(10), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(17), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(9), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(11), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(14), err => {\n if (err) {\n return\n }\n });\n break;\n case \"black\":\n fs.writeFile(filepath + 'pin_external', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(24), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(22), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(17), err => {\n if (err) {\n return\n }\n });\n break;\n case \"custom\":\n break;\n }\n\n fs.writeFile(filepath + file, current_choice, err => {\n if (err) {\n return\n }\n });\n}\n\nmsg.status = 'The new ' + current_choice + ' shield has been configured'\nmsg.topic = msg.status\nmsg.payload = 'Do you want to reboot now to apply the changes?'\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 460, + "y": 3840, + "wires": [ + [ + "137f032887544d74" + ] + ] + }, + { + "id": "137f032887544d74", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": false, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Reboot", + "x": 600, + "y": 3840, + "wires": [ + [ + "d2db49796fe0da79", + "d0d6820224b0ab0f" + ] + ] + }, + { + "id": "d2db49796fe0da79", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "print(msg['payload'])\nreturn msg", + "outputs": 1, + "x": 790, + "y": 3840, + "wires": [ + [] + ] + }, + { + "id": "d0d6820224b0ab0f", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 3720, + "wires": [] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", "z": "a5557543ccff5889", "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", "outputs": 1, "x": 260, "y": 140, @@ -8665,7 +8959,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -8700,7 +8994,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -8984,7 +9278,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = load_str('openscan_version')\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, @@ -9066,8 +9360,8 @@ "z": "a5557543ccff5889", "name": "", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "50eeb3e362f9027f", + "960912e90ba5b5bc" ], "x": 95, "y": 700, @@ -9160,7 +9454,7 @@ "order": 9, "width": 6, "height": 1, - "format": "\n
Download error log\n
\n", + "format": "\n
Download error log\n
\n", "storeOutMessages": false, "fwdInMessages": false, "resendOnRefresh": false, @@ -9374,5 +9668,111 @@ "wires": [ [] ] + }, + { + "id": "9b3e6a06c82a0f52", + "type": "link in", + "z": "87715429b0b1c9a3", + "name": "", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 55, + "y": 120, + "wires": [ + [ + "f128ca405d1e1e4d", + "07d7ce3dab5f1c11" + ] + ] + }, + { + "id": "cd0dc08fcb5968c8", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Successful Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 450, + "y": 180, + "wires": [] + }, + { + "id": "f128ca405d1e1e4d", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Successful Scans", + "x": 210, + "y": 180, + "wires": [ + [ + "cd0dc08fcb5968c8" + ], + [], + [] + ] + }, + { + "id": "b91b4d65f2090793", + "type": "ui_text", + "z": "87715429b0b1c9a3", + "group": "ac59b8fb186de073", + "order": 0, + "width": 0, + "height": 0, + "name": "", + "label": "Aborted Scans", + "format": "{{msg.payload}}", + "layout": "row-spread", + "className": "", + "x": 440, + "y": 120, + "wires": [] + }, + { + "id": "07d7ce3dab5f1c11", + "type": "exec", + "z": "87715429b0b1c9a3", + "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "Aborted Scans", + "x": 200, + "y": 120, + "wires": [ + [ + "b91b4d65f2090793" + ], + [], + [] + ] + }, + { + "id": "5b3aa9a71591ba34", + "type": "comment", + "z": "87715429b0b1c9a3", + "name": "Statistics", + "info": "", + "x": 100, + "y": 40, + "wires": [] } ] \ No newline at end of file diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json index 9df09ab..f696308 100644 --- a/update/2024-12o/update.json +++ b/update/2024-12o/update.json @@ -28,7 +28,7 @@ "6": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 327660 + "filesize": 341588 }, "7": { "src": "meanwhile/settings.js", From 56b32111823530ec251fbfa9d6d91d8519dd2180 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 29 Sep 2024 21:15:32 +0200 Subject: [PATCH 19/38] ensure that when pin_tt_endstop has been eliminated the scanner keeps working --- update/2024-12o/meanwhile/OpenScan.py | 9 +++++++-- update/2024-12o/update.json | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/update/2024-12o/meanwhile/OpenScan.py b/update/2024-12o/meanwhile/OpenScan.py index e634511..cbce61c 100644 --- a/update/2024-12o/meanwhile/OpenScan.py +++ b/update/2024-12o/meanwhile/OpenScan.py @@ -148,13 +148,18 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): dir = load_int(motor + '_dir') ramp = load_int(motor + '_accramp') acc = load_float(motor + '_acc') + if motor != 'tt': + ES_pin = load_int('pin_' + motor + '_endstop') + else: + ES_pin = '33' delay_init = load_float(motor + '_delay') delay = delay_init step_count=int(angle*spr/360) * dir GPIO.setup(dirpin, GPIO.OUT) GPIO.setup(steppin, GPIO.OUT) - GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + if motor != 'tt': + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) if (step_count>0): GPIO.output(dirpin, GPIO.HIGH) @@ -162,7 +167,7 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): GPIO.output(dirpin, GPIO.LOW) step_count=-step_count for x in range(step_count): - if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state and motor != 'tt': i = 0 while i <= 10: if GPIO.input(ES_pin) == ES_start_state: diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json index f696308..af5ecac 100644 --- a/update/2024-12o/update.json +++ b/update/2024-12o/update.json @@ -78,7 +78,7 @@ "1": { "src": "meanwhile/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10249 + "filesize": 10396 }, "2": { "src": "meanwhile/OpenScanStatistics.py", From 82714cf23c8c1d15b62cc8ad3ab2e7c0342255e2 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 29 Sep 2024 22:25:56 +0200 Subject: [PATCH 20/38] reorganize settings section to split rotor and turntable, add option to select tt to run first --- update/2024-12o/meanwhile/flows.json | 278 +++++++++++++++++---------- update/2024-12o/update.json | 2 +- 2 files changed, 179 insertions(+), 101 deletions(-) diff --git a/update/2024-12o/meanwhile/flows.json b/update/2024-12o/meanwhile/flows.json index 3ff3e38..4a5261f 100644 --- a/update/2024-12o/meanwhile/flows.json +++ b/update/2024-12o/meanwhile/flows.json @@ -246,17 +246,6 @@ "collapse": false, "className": "" }, - { - "id": "5b3e5aca21140e9a", - "type": "ui_group", - "name": "Update", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, { "id": "b3150b13e34b1fe8", "type": "ui_tab", @@ -332,7 +321,7 @@ { "id": "7a3279eea439bcdd", "type": "ui_group", - "name": "Motor", + "name": "Rotor", "tab": "457102eadc9ddb6c", "order": 7, "disp": true, @@ -471,16 +460,6 @@ "width": 4, "height": 1 }, - { - "id": "0799b02d12fc3a14", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "7a3279eea439bcdd", - "order": 25, - "width": 6, - "height": 1 - }, { "id": "220493325bb79987", "type": "ui_group", @@ -527,7 +506,7 @@ "id": "e357ef02.ef3cb", "type": "ui_group", "name": "Page 1", - "tab": "4bc17c6e.b74934", + "tab": "", "order": 2, "disp": false, "width": "6", @@ -546,13 +525,24 @@ "className": "" }, { - "id": "4bc17c6e.b74934", - "type": "ui_tab", - "name": "Page 1", - "icon": "wi-wu-tstorms", - "order": 5, - "disabled": false, - "hidden": false + "id": "f622137daacdfebe", + "type": "ui_group", + "name": "Group 3", + "tab": "b3150b13e34b1fe8", + "order": 3, + "disp": true, + "width": 6 + }, + { + "id": "38d121ea5b2bd77d", + "type": "ui_group", + "name": "Turntable", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, + "width": "6", + "collapse": false, + "className": "" }, { "id": "bc4e2c03859196c3", @@ -1057,7 +1047,7 @@ "type": "ui_button", "z": "e6f4d02efb300ea9", "name": "", - "group": "5b3e5aca21140e9a", + "group": "", "order": 1, "width": 6, "height": 3, @@ -2292,7 +2282,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -2806,8 +2796,8 @@ "complete": "false", "statusVal": "", "statusType": "auto", - "x": 620, - "y": 1000, + "x": 560, + "y": 960, "wires": [] }, { @@ -4840,8 +4830,8 @@ "name": "tt delay", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 30, + "group": "38d121ea5b2bd77d", + "order": 7, "width": 3, "height": 1, "passthru": false, @@ -4945,10 +4935,10 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 19, + "order": 17, "width": 3, "height": 1, - "name": "rotor Accramp", + "name": "rotor_acc_label", "label": "Acceleration ramp", "format": "", "layout": "row-left", @@ -4962,15 +4952,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 13, + "order": 19, "width": 3, "height": 1, - "name": "rotor_Steps per Rotation", + "name": "rotor_accramp_label", "label": "Steps per Rotation", "format": "", "layout": "row-spread", "className": "", - "x": 810, + "x": 800, "y": 2180, "wires": [] }, @@ -4979,15 +4969,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 17, + "order": 15, "width": 3, "height": 1, - "name": "rotor Acc", + "name": "rotor_delay_label", "label": "Acceleration", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 790, "y": 2100, "wires": [] }, @@ -4996,15 +4986,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 15, + "order": 13, "width": 3, "height": 1, - "name": "rotor_delay", + "name": "rotor_steps_rotation_label", "label": "Delay", "format": "", "layout": "row-left", "className": "", - "x": 770, + "x": 810, "y": 2060, "wires": [] }, @@ -5012,8 +5002,8 @@ "id": "355e89ab4e5484e4", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 26, + "group": "38d121ea5b2bd77d", + "order": 1, "width": 6, "height": 1, "name": "tt", @@ -5032,8 +5022,8 @@ "name": "tt_acc", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 32, + "group": "38d121ea5b2bd77d", + "order": 9, "width": 3, "height": 1, "passthru": false, @@ -5059,8 +5049,8 @@ "name": "tt_accramp", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 34, + "group": "38d121ea5b2bd77d", + "order": 11, "width": 3, "height": 1, "passthru": false, @@ -5086,8 +5076,8 @@ "name": "tt_stepsperrotation", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 28, + "group": "38d121ea5b2bd77d", + "order": 5, "width": 3, "height": 1, "passthru": false, @@ -5109,16 +5099,16 @@ "id": "18e5918748660109", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 33, + "group": "38d121ea5b2bd77d", + "order": 10, "width": 3, "height": 1, - "name": "ttAccramp", + "name": "tt_accramp_label", "label": "Acceleration ramp", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 780, "y": 2420, "wires": [] }, @@ -5126,16 +5116,16 @@ "id": "8e805244dc1899e8", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 27, + "group": "38d121ea5b2bd77d", + "order": 4, "width": 3, "height": 1, - "name": "tt_steps per Rotation", + "name": "tt_stepsperrotation_label", "label": "Steps per Rotation", "format": "", "layout": "row-spread", "className": "", - "x": 800, + "x": 810, "y": 2300, "wires": [] }, @@ -5143,16 +5133,16 @@ "id": "a09e5fbea861bfb1", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 31, + "group": "38d121ea5b2bd77d", + "order": 8, "width": 3, "height": 1, - "name": "tt Acc", + "name": "tt_acc_label", "label": "Acceleration", "format": "", "layout": "row-left", "className": "", - "x": 750, + "x": 770, "y": 2380, "wires": [] }, @@ -5160,16 +5150,16 @@ "id": "7b06448b3b222011", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 29, + "group": "38d121ea5b2bd77d", + "order": 6, "width": 3, "height": 1, - "name": "tt_delay", + "name": "tt_delay_label", "label": "Delay", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 780, "y": 2340, "wires": [] }, @@ -5208,12 +5198,12 @@ "order": 21, "width": 3, "height": 1, - "name": "rotor_angle", + "name": "rotor_angle_label", "label": "Manual angle", "format": "", "layout": "row-spread", "className": "", - "x": 770, + "x": 790, "y": 2220, "wires": [] }, @@ -5224,8 +5214,8 @@ "name": "tt_angle", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 36, + "group": "38d121ea5b2bd77d", + "order": 13, "width": 3, "height": 1, "passthru": false, @@ -5248,16 +5238,16 @@ "id": "96a9febc0928b6f0", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 35, + "group": "38d121ea5b2bd77d", + "order": 12, "width": 3, "height": 1, - "name": "tt_angle", + "name": "tt_angle_label", "label": "Manual angle", "format": "", "layout": "row-spread", "className": "", - "x": 760, + "x": 780, "y": 2460, "wires": [] }, @@ -5285,8 +5275,8 @@ "name": "tt_dir", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 38, + "group": "38d121ea5b2bd77d", + "order": 15, "width": 3, "height": 1, "passthru": false, @@ -5336,16 +5326,16 @@ "id": "6b0d58943ecb8bb2", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 37, + "group": "38d121ea5b2bd77d", + "order": 14, "width": 3, "height": 1, - "name": "tt_dir", + "name": "tt_dir_label", "label": "Direction", "format": "", "layout": "row-spread", "className": "", - "x": 750, + "x": 770, "y": 2500, "wires": [] }, @@ -5357,12 +5347,12 @@ "order": 23, "width": 3, "height": 1, - "name": "rotor_dir", + "name": "rotor_dir_label", "label": "Direction", "format": "", "layout": "row-spread", "className": "", - "x": 760, + "x": 780, "y": 2260, "wires": [] }, @@ -5414,7 +5404,7 @@ "max": "1", "step": "0.02", "className": "", - "x": 430, + "x": 450, "y": 2600, "wires": [ [ @@ -5441,7 +5431,7 @@ "max": "10", "step": "0.1", "className": "", - "x": 400, + "x": 420, "y": 2640, "wires": [ [ @@ -7197,12 +7187,12 @@ "order": 5, "width": 3, "height": 1, - "name": "rotor_anglemin", + "name": "rotor_anglemin_label", "label": "Min Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1820, "wires": [] }, @@ -7384,12 +7374,12 @@ "order": 7, "width": 3, "height": 1, - "name": "rotor_anglemax", + "name": "rotor_anglemax_label", "label": "Max Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1860, "wires": [] }, @@ -7401,12 +7391,12 @@ "order": 3, "width": 3, "height": 1, - "name": "rotor_anglestart", + "name": "rotor_anglestart_label", "label": "Start Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1900, "wires": [] }, @@ -7480,7 +7470,8 @@ "5b02160c33605ae7", "304c135ec09801e3", "f036424d79645761", - "b7db72b7f0599ebd" + "b7db72b7f0599ebd", + "a74d78a1d186a833" ] ] }, @@ -8148,12 +8139,12 @@ "order": 11, "width": 3, "height": 1, - "name": "endstop_angle", + "name": "rotor_endstop_angle_label", "label": "Endstop angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 820, "y": 2020, "wires": [] }, @@ -8272,12 +8263,12 @@ "order": 9, "width": 3, "height": 1, - "name": "rotor_enable_endstop", + "name": "rotor_enable_endstop_label", "label": "Enable Endstop", "format": "", "layout": "row-left", "className": "", - "x": 800, + "x": 820, "y": 1940, "wires": [] }, @@ -8837,6 +8828,93 @@ "y": 3720, "wires": [] }, + { + "id": "a74d78a1d186a833", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotate_tt_first'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1760, + "wires": [ + [ + "4e1b38e60f4179b0" + ] + ] + }, + { + "id": "4e1b38e60f4179b0", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotate_tt_first", + "label": "", + "tooltip": "", + "group": "38d121ea5b2bd77d", + "order": 3, + "width": "3", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 430, + "y": 1760, + "wires": [ + [ + "0e14711b77c43c23" + ] + ] + }, + { + "id": "0e14711b77c43c23", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotate_tt_first'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1760, + "wires": [ + [] + ] + }, + { + "id": "b1d13cc1ebec82d7", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "38d121ea5b2bd77d", + "order": 2, + "width": 3, + "height": 1, + "name": "rotate_tt_first_label", + "label": "Rotate turntable first", + "format": "", + "layout": "row-left", + "className": "", + "x": 790, + "y": 1760, + "wires": [] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json index af5ecac..d2ebe08 100644 --- a/update/2024-12o/update.json +++ b/update/2024-12o/update.json @@ -103,7 +103,7 @@ "6": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 341591 + "filesize": 344127 }, "7": { "src": "meanwhile/settings.js", From 3018e874c9c54773c390cfa477b531efa635dda0 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 29 Sep 2024 23:01:11 +0200 Subject: [PATCH 21/38] u0pdate statistics with openscan versionig --- update/2024-12o/meanwhile/OpenScanStatistics.py | 6 +++--- update/2024-12o/meanwhile/flows.json | 2 +- update/2024-12o/update.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/update/2024-12o/meanwhile/OpenScanStatistics.py b/update/2024-12o/meanwhile/OpenScanStatistics.py index 68005af..3ddb3b4 100755 --- a/update/2024-12o/meanwhile/OpenScanStatistics.py +++ b/update/2024-12o/meanwhile/OpenScanStatistics.py @@ -3,10 +3,10 @@ class ScanStatistics: def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): self.filename = filename - self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + self.header = ["arch", "openscan version", "openscan branch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] - def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): - data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] + def write_statistics(self, arch, openscan_version, openscan_branch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): + data = [arch, openscan_version, openscan_branch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] with open(self.filename, "a", newline='') as csv_file: csv_writer = csv.writer(csv_file, delimiter=';') diff --git a/update/2024-12o/meanwhile/flows.json b/update/2024-12o/meanwhile/flows.json index 4a5261f..d572384 100644 --- a/update/2024-12o/meanwhile/flows.json +++ b/update/2024-12o/meanwhile/flows.json @@ -2282,7 +2282,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"update_type\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, openscan_version, openscan_branch, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, openscan_version, openscan_branch, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json index d2ebe08..1823d42 100644 --- a/update/2024-12o/update.json +++ b/update/2024-12o/update.json @@ -83,7 +83,7 @@ "2": { "src": "meanwhile/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", - "filesize": 793 + "filesize": 902 }, "3": { "src": "meanwhile/config.txt", @@ -103,7 +103,7 @@ "6": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 344127 + "filesize": 344293 }, "7": { "src": "meanwhile/settings.js", From 29533558e1716354079fbe1c3b7e4ee78267ffbb Mon Sep 17 00:00:00 2001 From: Stealth Date: Mon, 30 Sep 2024 18:31:18 +0200 Subject: [PATCH 22/38] add more statistics! --- .../2024-12o/meanwhile/OpenScanStatistics.py | 50 +++++++++++++------ update/2024-12o/meanwhile/flows.json | 40 ++++----------- update/2024-12o/update.json | 4 +- 3 files changed, 46 insertions(+), 48 deletions(-) diff --git a/update/2024-12o/meanwhile/OpenScanStatistics.py b/update/2024-12o/meanwhile/OpenScanStatistics.py index 3ddb3b4..8adf9dd 100755 --- a/update/2024-12o/meanwhile/OpenScanStatistics.py +++ b/update/2024-12o/meanwhile/OpenScanStatistics.py @@ -1,18 +1,38 @@ -import csv +import json +import os +from datetime import datetime +from dataclasses import dataclass + +@dataclass +class ScanData: + arch: str + openscan_version: str + openscan_branch: str + shield: str + date_init: str # Format: YYYY-MM-DD HH:MM + date_end: str # Format: YYYY-MM-DD HH:MM + num_photos: int + done_photos: int + camera: str + stack_size: int + telegram_enabled: bool + delete_aborted: bool + endstop_enabled: bool + group_stack_photos: bool + aborted: bool class ScanStatistics: - def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): - self.filename = filename - self.header = ["arch", "openscan version", "openscan branch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + def __init__(self, filename: str = "/home/pi/OpenScan/statistics") -> None: + self.filename: str = filename + + def write_statistics(self, scan_data: ScanData) -> None: + data: dict = scan_data.__dict__ # Convert dataclass to dictionary + + # Parse date_init to get year and month + date_object: datetime = datetime.strptime(scan_data.date_init, "%Y-%m-%d %H:%M") + record_filename: str = os.path.join(self.filename, f"statistics-{date_object.year}-{date_object.month:02d}.json") - def write_statistics(self, arch, openscan_version, openscan_branch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): - data = [arch, openscan_version, openscan_branch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] - - with open(self.filename, "a", newline='') as csv_file: - csv_writer = csv.writer(csv_file, delimiter=';') - - # Write header if file is empty - if csv_file.tell() == 0: - csv_writer.writerow(self.header) - - csv_writer.writerow(data) + # Append the new data as a new line + with open(record_filename, "a") as json_file: + json.dump(data, json_file, separators=(',', ':'), indent=None) # Collapsed JSON + json_file.write('\n') # Add a newline after each entry \ No newline at end of file diff --git a/update/2024-12o/meanwhile/flows.json b/update/2024-12o/meanwhile/flows.json index d572384..c8f8fa9 100644 --- a/update/2024-12o/meanwhile/flows.json +++ b/update/2024-12o/meanwhile/flows.json @@ -502,28 +502,6 @@ "collapse": false, "className": "" }, - { - "id": "e357ef02.ef3cb", - "type": "ui_group", - "name": "Page 1", - "tab": "", - "order": 2, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, - { - "id": "c9165343995892c6", - "type": "ui_group", - "name": "Page 2", - "tab": "", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, { "id": "f622137daacdfebe", "type": "ui_group", @@ -2282,7 +2260,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"update_type\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, openscan_version, openscan_branch, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, openscan_version, openscan_branch, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics, ScanData\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"update_type\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nendstop_enable = load_bool('rotor_enable_endstop')\nif endstop_enable:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\n\nif counter == photocount:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n aborted=False\nelse:\n aborted=True\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nscan_data = ScanData(\n arch=architecture,\n openscan_version=openscan_version,\n openscan_branch=openscan_branch,\n shield=shield,\n date_init=date_init,\n date_end=date_end,\n num_photos=photocount,\n done_photos=counter,\n camera=camera_model,\n stack_size=stacksize,\n telegram_enabled=telegram_enable,\n delete_aborted=delete_aborted,\n endstop_enabled=endstop_enable,\n group_stack_photos=group_stack_photos,\n aborted=aborted\n)\n\nstats.write_statistics(scan_data)\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -9778,15 +9756,15 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 180, + "x": 410, + "y": 100, "wires": [] }, { "id": "f128ca405d1e1e4d", "type": "exec", "z": "87715429b0b1c9a3", - "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "command": "cat /home/pi/OpenScan/statistics/statistics*.json|grep -i \\\"aborted\\\":false|wc -l", "addpay": "", "append": "", "useSpawn": "false", @@ -9795,7 +9773,7 @@ "oldrc": false, "name": "Successful Scans", "x": 210, - "y": 180, + "y": 120, "wires": [ [ "cd0dc08fcb5968c8" @@ -9817,15 +9795,15 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 440, - "y": 120, + "x": 400, + "y": 160, "wires": [] }, { "id": "07d7ce3dab5f1c11", "type": "exec", "z": "87715429b0b1c9a3", - "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "command": "cat /home/pi/OpenScan/statistics/statistics*.json|grep -i \\\"aborted\\\":true|wc -l", "addpay": "", "append": "", "useSpawn": "false", @@ -9834,7 +9812,7 @@ "oldrc": false, "name": "Aborted Scans", "x": 200, - "y": 120, + "y": 180, "wires": [ [ "b91b4d65f2090793" diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json index 1823d42..b000458 100644 --- a/update/2024-12o/update.json +++ b/update/2024-12o/update.json @@ -83,7 +83,7 @@ "2": { "src": "meanwhile/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", - "filesize": 902 + "filesize": 1288 }, "3": { "src": "meanwhile/config.txt", @@ -103,7 +103,7 @@ "6": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 344293 + "filesize": 344141 }, "7": { "src": "meanwhile/settings.js", From b75d126d6596520839e5fe0cfaedf10aa294c2d6 Mon Sep 17 00:00:00 2001 From: Stealth Date: Mon, 30 Sep 2024 19:55:48 +0200 Subject: [PATCH 23/38] stable branch --- update/2024-12o/beta/OpenScan.py | 9 +- update/2024-12o/beta/OpenScanStatistics.py | 50 +- update/2024-12o/beta/flows.json | 802 ++++++++++++------- update/2024-12o/meanwhile/flows.json | 71 +- update/2024-12o/stable/OpenScan.py | 9 +- update/2024-12o/stable/OpenScanStatistics.py | 50 +- update/2024-12o/stable/flows.json | 414 +++++----- update/2024-12o/update.json | 46 +- 8 files changed, 833 insertions(+), 618 deletions(-) diff --git a/update/2024-12o/beta/OpenScan.py b/update/2024-12o/beta/OpenScan.py index e634511..cbce61c 100644 --- a/update/2024-12o/beta/OpenScan.py +++ b/update/2024-12o/beta/OpenScan.py @@ -148,13 +148,18 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): dir = load_int(motor + '_dir') ramp = load_int(motor + '_accramp') acc = load_float(motor + '_acc') + if motor != 'tt': + ES_pin = load_int('pin_' + motor + '_endstop') + else: + ES_pin = '33' delay_init = load_float(motor + '_delay') delay = delay_init step_count=int(angle*spr/360) * dir GPIO.setup(dirpin, GPIO.OUT) GPIO.setup(steppin, GPIO.OUT) - GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + if motor != 'tt': + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) if (step_count>0): GPIO.output(dirpin, GPIO.HIGH) @@ -162,7 +167,7 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): GPIO.output(dirpin, GPIO.LOW) step_count=-step_count for x in range(step_count): - if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state and motor != 'tt': i = 0 while i <= 10: if GPIO.input(ES_pin) == ES_start_state: diff --git a/update/2024-12o/beta/OpenScanStatistics.py b/update/2024-12o/beta/OpenScanStatistics.py index 68005af..8adf9dd 100755 --- a/update/2024-12o/beta/OpenScanStatistics.py +++ b/update/2024-12o/beta/OpenScanStatistics.py @@ -1,18 +1,38 @@ -import csv +import json +import os +from datetime import datetime +from dataclasses import dataclass + +@dataclass +class ScanData: + arch: str + openscan_version: str + openscan_branch: str + shield: str + date_init: str # Format: YYYY-MM-DD HH:MM + date_end: str # Format: YYYY-MM-DD HH:MM + num_photos: int + done_photos: int + camera: str + stack_size: int + telegram_enabled: bool + delete_aborted: bool + endstop_enabled: bool + group_stack_photos: bool + aborted: bool class ScanStatistics: - def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): - self.filename = filename - self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + def __init__(self, filename: str = "/home/pi/OpenScan/statistics") -> None: + self.filename: str = filename + + def write_statistics(self, scan_data: ScanData) -> None: + data: dict = scan_data.__dict__ # Convert dataclass to dictionary + + # Parse date_init to get year and month + date_object: datetime = datetime.strptime(scan_data.date_init, "%Y-%m-%d %H:%M") + record_filename: str = os.path.join(self.filename, f"statistics-{date_object.year}-{date_object.month:02d}.json") - def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): - data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] - - with open(self.filename, "a", newline='') as csv_file: - csv_writer = csv.writer(csv_file, delimiter=';') - - # Write header if file is empty - if csv_file.tell() == 0: - csv_writer.writerow(self.header) - - csv_writer.writerow(data) + # Append the new data as a new line + with open(record_filename, "a") as json_file: + json.dump(data, json_file, separators=(',', ':'), indent=None) # Collapsed JSON + json_file.write('\n') # Add a newline after each entry \ No newline at end of file diff --git a/update/2024-12o/beta/flows.json b/update/2024-12o/beta/flows.json index e0db7f2..ee66e6c 100644 --- a/update/2024-12o/beta/flows.json +++ b/update/2024-12o/beta/flows.json @@ -173,7 +173,7 @@ "link": "https://openscan-org.github.io/OpenScan-Doc/", "icon": "fa-bookmark", "target": "iframe", - "order": 6 + "order": 8 }, { "id": "23f75a8768250ce8", @@ -182,7 +182,7 @@ "link": "https://www.patreon.com/OpenScan", "icon": "fa-bookmark", "target": "newtab", - "order": 5 + "order": 7 }, { "id": "b5fdd57b.15eda8", @@ -246,17 +246,6 @@ "collapse": false, "className": "" }, - { - "id": "5b3e5aca21140e9a", - "type": "ui_group", - "name": "Update", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, { "id": "b3150b13e34b1fe8", "type": "ui_tab", @@ -312,7 +301,7 @@ "type": "ui_group", "name": "Network", "tab": "457102eadc9ddb6c", - "order": 2, + "order": 4, "disp": true, "width": "6", "collapse": true, @@ -323,7 +312,7 @@ "type": "ui_group", "name": "Pinout", "tab": "457102eadc9ddb6c", - "order": 6, + "order": 3, "disp": true, "width": "6", "collapse": true, @@ -332,9 +321,9 @@ { "id": "7a3279eea439bcdd", "type": "ui_group", - "name": "Motor", + "name": "Rotor", "tab": "457102eadc9ddb6c", - "order": 5, + "order": 7, "disp": true, "width": "6", "collapse": true, @@ -345,7 +334,7 @@ "type": "ui_group", "name": "Camera", "tab": "457102eadc9ddb6c", - "order": 4, + "order": 6, "disp": true, "width": "6", "collapse": true, @@ -356,7 +345,7 @@ "type": "ui_group", "name": "OpenScanCloud", "tab": "457102eadc9ddb6c", - "order": 3, + "order": 5, "disp": true, "width": "6", "collapse": false, @@ -367,7 +356,7 @@ "type": "ui_tab", "name": "Settings", "icon": "dashboard", - "order": 4, + "order": 5, "disabled": false, "hidden": false }, @@ -471,22 +460,12 @@ "width": 4, "height": 1 }, - { - "id": "0799b02d12fc3a14", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "7a3279eea439bcdd", - "order": 25, - "width": 6, - "height": 1 - }, { "id": "220493325bb79987", "type": "ui_group", "name": "Messaging", "tab": "457102eadc9ddb6c", - "order": 7, + "order": 8, "disp": true, "width": "6", "collapse": false, @@ -495,7 +474,7 @@ { "id": "ac59b8fb186de073", "type": "ui_group", - "name": "[Statistics]", + "name": "Statistics", "tab": "656b4eb8b15dab8f", "order": 3, "disp": true, @@ -508,9 +487,41 @@ "type": "ui_tab", "name": "Statistics", "icon": "dashboard", + "order": 6, "disabled": false, "hidden": false }, + { + "id": "0b244f698c7ac9a2", + "type": "ui_group", + "name": "Shield Type", + "tab": "457102eadc9ddb6c", + "order": 2, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, + { + "id": "f622137daacdfebe", + "type": "ui_group", + "name": "Group 3", + "tab": "b3150b13e34b1fe8", + "order": 3, + "disp": true, + "width": 6 + }, + { + "id": "38d121ea5b2bd77d", + "type": "ui_group", + "name": "Turntable", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -533,7 +544,7 @@ "payload": "", "payloadType": "date", "x": 100, - "y": 460, + "y": 520, "wires": [ [ "949bafced17d66d6" @@ -552,7 +563,7 @@ "finalize": "", "libs": [], "x": 250, - "y": 460, + "y": 520, "wires": [ [] ] @@ -588,7 +599,8 @@ "b1e2491c952f84c9", "fac6626127bba4f5", "bc2f0adaf72f97e9", - "ac242724fe7605a6" + "ac242724fe7605a6", + "d81572486f15cd7a" ] ] }, @@ -597,7 +609,7 @@ "type": "function", "z": "e6f4d02efb300ea9", "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'openscan_branch':'stable',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -664,7 +676,8 @@ "d1efcd5fa9d25785", "da61581182b7299e", "2afb6a45c73fa244", - "9b3e6a06c82a0f52" + "9b3e6a06c82a0f52", + "fbc5fc2e65311f8b" ], "x": 645, "y": 60, @@ -705,7 +718,7 @@ "payload": "", "payloadType": "str", "x": 100, - "y": 380, + "y": 440, "wires": [ [ "6c6ef2255a7d39e5" @@ -724,7 +737,7 @@ "6bf8344af427a6ba" ], "x": 205, - "y": 380, + "y": 440, "wires": [] }, { @@ -785,9 +798,9 @@ "name": "enable", "mode": "link", "links": [ + "65518f3d4e3095e5", "8367cfa0bf5bc5df", - "c8b93b42c720b9cf", - "65518f3d4e3095e5" + "c8b93b42c720b9cf" ], "x": 345, "y": 280, @@ -864,8 +877,8 @@ "resendOnRefresh": true, "templateScope": "global", "className": "", - "x": 580, - "y": 140, + "x": 650, + "y": 360, "wires": [ [] ] @@ -904,7 +917,7 @@ "topic": "", "topicType": "str", "x": 90, - "y": 540, + "y": 600, "wires": [ [ "62cd5288.2805fc" @@ -932,7 +945,7 @@ "topic": "", "topicType": "str", "x": 100, - "y": 620, + "y": 680, "wires": [ [ "62cd5288.2805fc" @@ -946,7 +959,7 @@ "name": "", "events": "all", "x": 280, - "y": 540, + "y": 600, "wires": [ [] ] @@ -972,7 +985,7 @@ "topic": "", "topicType": "str", "x": 90, - "y": 580, + "y": 640, "wires": [ [ "62cd5288.2805fc" @@ -1000,7 +1013,7 @@ "topic": "", "topicType": "str", "x": 110, - "y": 660, + "y": 720, "wires": [ [ "62cd5288.2805fc" @@ -1012,7 +1025,7 @@ "type": "ui_button", "z": "e6f4d02efb300ea9", "name": "", - "group": "5b3e5aca21140e9a", + "group": "", "order": 1, "width": 6, "height": 3, @@ -1118,35 +1131,23 @@ "name": "started1s", "mode": "link", "links": [ + "1dffb799fdf10cbc", + "2afb6a45c73fa244", + "2e9b29c70969cf01", "2f4c0f98.dee2", - "397ab7f44b893c89", - "65145c939b6647e2", - "65b38bfeb3fee710", - "6d1e12f51f9af0b6", - "788fabff98c7973c", - "9b2bc9849aee310b", - "a1e14624058e74cd", - "a67c18aaca2f5fa5", - "bd80ec228fb9a86d", - "cc9c4092edeb43cc", - "d3fc91d87d5d5f62", - "d7c1fb4c028b21a5", - "e5f38b4a07a5e278", - "f0b355967b33dfee", - "d0104e0163745993", - "5e7d5e4335d37794", - "b4c843620c251c43", "3876d5cbd248592b", - "a4c81754c148b86f", - "2e9b29c70969cf01", - "2477f81cddc8fa31", - "29036b35dfd672c6", "592ec13d8f8923a9", + "5e7d5e4335d37794", + "9b3e6a06c82a0f52", + "9fd259de91de1da1", + "b4c843620c251c43", "cb40b9341bd22a28", + "d0104e0163745993", "d1efcd5fa9d25785", "da61581182b7299e", - "2afb6a45c73fa244", - "9b3e6a06c82a0f52" + "e5f38b4a07a5e278", + "fbc5fc2e65311f8b", + "fd0258418489839d" ], "x": 1015, "y": 540, @@ -1236,13 +1237,59 @@ "topic": "topic", "topicType": "msg", "x": 100, - "y": 700, + "y": 760, "wires": [ [ "62cd5288.2805fc" ] ] }, + { + "id": "d81572486f15cd7a", + "type": "function", + "z": "e6f4d02efb300ea9", + "name": "loadl", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = String(data);\nflow.set('openscan_version',data)\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 250, + "y": 360, + "wires": [ + [ + "b1b0ccb783dd5882" + ] + ] + }, + { + "id": "b1b0ccb783dd5882", + "type": "change", + "z": "e6f4d02efb300ea9", + "name": "openscan_version", + "rules": [ + { + "t": "set", + "p": "openscan_version", + "pt": "flow", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 415, + "y": 360, + "wires": [ + [ + "0d8c6bc7887fb3c2" + ] + ] + }, { "id": "6a3d9acbe097a3d2", "type": "function", @@ -2195,7 +2242,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = \"green\"\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n return\n \ncounter2 = 0\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\n# Delete the status.json file\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics, ScanData\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"openscan_branch\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nendstop_enable = load_bool('rotor_enable_endstop')\nif endstop_enable:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\n\nif counter == photocount:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n aborted=False\nelse:\n aborted=True\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nscan_data = ScanData(\n arch=architecture,\n openscan_version=openscan_version,\n openscan_branch=openscan_branch,\n shield=shield,\n date_init=date_init,\n date_end=date_end,\n num_photos=photocount,\n done_photos=counter,\n camera=camera_model,\n stack_size=stacksize,\n telegram_enabled=telegram_enable,\n delete_aborted=delete_aborted,\n endstop_enabled=endstop_enable,\n group_stack_photos=group_stack_photos,\n aborted=aborted\n)\n\nstats.write_statistics(scan_data)\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -2709,8 +2756,8 @@ "complete": "false", "statusVal": "", "statusType": "auto", - "x": 620, - "y": 1000, + "x": 560, + "y": 960, "wires": [] }, { @@ -4743,8 +4790,8 @@ "name": "tt delay", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 30, + "group": "38d121ea5b2bd77d", + "order": 7, "width": 3, "height": 1, "passthru": false, @@ -4848,10 +4895,10 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 19, + "order": 17, "width": 3, "height": 1, - "name": "rotor Accramp", + "name": "rotor_acc_label", "label": "Acceleration ramp", "format": "", "layout": "row-left", @@ -4865,15 +4912,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 13, + "order": 19, "width": 3, "height": 1, - "name": "rotor_Steps per Rotation", + "name": "rotor_accramp_label", "label": "Steps per Rotation", "format": "", "layout": "row-spread", "className": "", - "x": 810, + "x": 800, "y": 2180, "wires": [] }, @@ -4882,15 +4929,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 17, + "order": 15, "width": 3, "height": 1, - "name": "rotor Acc", + "name": "rotor_delay_label", "label": "Acceleration", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 790, "y": 2100, "wires": [] }, @@ -4899,15 +4946,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 15, + "order": 13, "width": 3, "height": 1, - "name": "rotor_delay", + "name": "rotor_steps_rotation_label", "label": "Delay", "format": "", "layout": "row-left", "className": "", - "x": 770, + "x": 810, "y": 2060, "wires": [] }, @@ -4915,8 +4962,8 @@ "id": "355e89ab4e5484e4", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 26, + "group": "38d121ea5b2bd77d", + "order": 1, "width": 6, "height": 1, "name": "tt", @@ -4935,8 +4982,8 @@ "name": "tt_acc", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 32, + "group": "38d121ea5b2bd77d", + "order": 9, "width": 3, "height": 1, "passthru": false, @@ -4962,8 +5009,8 @@ "name": "tt_accramp", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 34, + "group": "38d121ea5b2bd77d", + "order": 11, "width": 3, "height": 1, "passthru": false, @@ -4989,8 +5036,8 @@ "name": "tt_stepsperrotation", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 28, + "group": "38d121ea5b2bd77d", + "order": 5, "width": 3, "height": 1, "passthru": false, @@ -5012,16 +5059,16 @@ "id": "18e5918748660109", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 33, + "group": "38d121ea5b2bd77d", + "order": 10, "width": 3, "height": 1, - "name": "ttAccramp", + "name": "tt_accramp_label", "label": "Acceleration ramp", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 780, "y": 2420, "wires": [] }, @@ -5029,16 +5076,16 @@ "id": "8e805244dc1899e8", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 27, + "group": "38d121ea5b2bd77d", + "order": 4, "width": 3, "height": 1, - "name": "tt_steps per Rotation", + "name": "tt_stepsperrotation_label", "label": "Steps per Rotation", "format": "", "layout": "row-spread", "className": "", - "x": 800, + "x": 810, "y": 2300, "wires": [] }, @@ -5046,16 +5093,16 @@ "id": "a09e5fbea861bfb1", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 31, + "group": "38d121ea5b2bd77d", + "order": 8, "width": 3, "height": 1, - "name": "tt Acc", + "name": "tt_acc_label", "label": "Acceleration", "format": "", "layout": "row-left", "className": "", - "x": 750, + "x": 770, "y": 2380, "wires": [] }, @@ -5063,16 +5110,16 @@ "id": "7b06448b3b222011", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 29, + "group": "38d121ea5b2bd77d", + "order": 6, "width": 3, "height": 1, - "name": "tt_delay", + "name": "tt_delay_label", "label": "Delay", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 780, "y": 2340, "wires": [] }, @@ -5111,12 +5158,12 @@ "order": 21, "width": 3, "height": 1, - "name": "rotor_angle", + "name": "rotor_angle_label", "label": "Manual angle", "format": "", "layout": "row-spread", "className": "", - "x": 770, + "x": 790, "y": 2220, "wires": [] }, @@ -5127,8 +5174,8 @@ "name": "tt_angle", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 36, + "group": "38d121ea5b2bd77d", + "order": 13, "width": 3, "height": 1, "passthru": false, @@ -5151,16 +5198,16 @@ "id": "96a9febc0928b6f0", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 35, + "group": "38d121ea5b2bd77d", + "order": 12, "width": 3, "height": 1, - "name": "tt_angle", + "name": "tt_angle_label", "label": "Manual angle", "format": "", "layout": "row-spread", "className": "", - "x": 760, + "x": 780, "y": 2460, "wires": [] }, @@ -5188,8 +5235,8 @@ "name": "tt_dir", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 38, + "group": "38d121ea5b2bd77d", + "order": 15, "width": 3, "height": 1, "passthru": false, @@ -5239,16 +5286,16 @@ "id": "6b0d58943ecb8bb2", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 37, + "group": "38d121ea5b2bd77d", + "order": 14, "width": 3, "height": 1, - "name": "tt_dir", + "name": "tt_dir_label", "label": "Direction", "format": "", "layout": "row-spread", "className": "", - "x": 750, + "x": 770, "y": 2500, "wires": [] }, @@ -5260,12 +5307,12 @@ "order": 23, "width": 3, "height": 1, - "name": "rotor_dir", + "name": "rotor_dir_label", "label": "Direction", "format": "", "layout": "row-spread", "className": "", - "x": 760, + "x": 780, "y": 2260, "wires": [] }, @@ -5317,7 +5364,7 @@ "max": "1", "step": "0.02", "className": "", - "x": 430, + "x": 450, "y": 2600, "wires": [ [ @@ -5344,7 +5391,7 @@ "max": "10", "step": "0.1", "className": "", - "x": 400, + "x": 420, "y": 2640, "wires": [ [ @@ -5907,49 +5954,6 @@ "y": 3320, "wires": [] }, - { - "id": "74e455136b5ca5dd", - "type": "ui_text_input", - "z": "e43a27722b508115", - "name": "endstop2", - "label": "", - "tooltip": "", - "group": "70d0be671bf03ca7", - "order": 21, - "width": 2, - "height": 1, - "passthru": false, - "mode": "number", - "delay": "0", - "topic": "topic", - "sendOnBlur": true, - "className": "", - "topicType": "msg", - "x": 400, - "y": 3360, - "wires": [ - [ - "a4a89668ce4c9f05" - ] - ] - }, - { - "id": "3a74f653800eb831", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "70d0be671bf03ca7", - "order": 20, - "width": 4, - "height": 1, - "name": "endstop2", - "label": "Endstop Turntable", - "format": "", - "layout": "row-spread", - "className": "", - "x": 740, - "y": 3360, - "wires": [] - }, { "id": "5fcef1cb2e9e4788", "type": "ui_toast", @@ -6253,9 +6257,9 @@ "type": "function", "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nvar steps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", "outputs": 1, - "noerr": 4, + "noerr": 0, "initialize": "", "finalize": "", "libs": [], @@ -7005,57 +7009,21 @@ ] }, { - "id": "21dc963d967d9c99", + "id": "22ef66b0e2058be2", "type": "function", "z": "e43a27722b508115", - "name": "loadI", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "name": "loadB", + "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 270, - "y": 3360, + "y": 360, "wires": [ [ - "74e455136b5ca5dd" - ] - ] - }, - { - "id": "a4a89668ce4c9f05", - "type": "function", - "z": "e43a27722b508115", - "name": "write", - "func": "var file = 'pin_tt_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\n\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 590, - "y": 3360, - "wires": [ - [] - ] - }, - { - "id": "22ef66b0e2058be2", - "type": "function", - "z": "e43a27722b508115", - "name": "loadB", - "func": "var file = 'ssh'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data;\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 270, - "y": 360, - "wires": [ - [ - "cb3437ec113e1b6f" + "cb3437ec113e1b6f" ] ] }, @@ -7083,9 +7051,9 @@ "type": "function", "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "noerr": 7, + "noerr": 0, "initialize": "", "finalize": "", "libs": [], @@ -7179,12 +7147,12 @@ "order": 5, "width": 3, "height": 1, - "name": "rotor_anglemin", + "name": "rotor_anglemin_label", "label": "Min Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1820, "wires": [] }, @@ -7366,12 +7334,12 @@ "order": 7, "width": 3, "height": 1, - "name": "rotor_anglemax", + "name": "rotor_anglemax_label", "label": "Max Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1860, "wires": [] }, @@ -7383,12 +7351,12 @@ "order": 3, "width": 3, "height": 1, - "name": "rotor_anglestart", + "name": "rotor_anglestart_label", "label": "Start Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1900, "wires": [] }, @@ -7462,7 +7430,8 @@ "5b02160c33605ae7", "304c135ec09801e3", "f036424d79645761", - "b7db72b7f0599ebd" + "b7db72b7f0599ebd", + "a74d78a1d186a833" ] ] }, @@ -7510,8 +7479,7 @@ "0456a9ec4c236c9e", "09d37ba08ec0f163", "37d954a4cf7e87ea", - "cc6dabe017a9c8a8", - "21dc963d967d9c99" + "cc6dabe017a9c8a8" ] ] }, @@ -8131,12 +8099,12 @@ "order": 11, "width": 3, "height": 1, - "name": "endstop_angle", + "name": "rotor_endstop_angle_label", "label": "Endstop angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 820, "y": 2020, "wires": [] }, @@ -8255,12 +8223,12 @@ "order": 9, "width": 3, "height": 1, - "name": "rotor_enable_endstop", + "name": "rotor_enable_endstop_label", "label": "Enable Endstop", "format": "", "layout": "row-left", "className": "", - "x": 800, + "x": 820, "y": 1940, "wires": [] }, @@ -8275,7 +8243,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 410, + "x": 450, "y": 1980, "wires": [ [ @@ -8386,7 +8354,7 @@ "name": "telegram_enable", "func": "from OpenScan import load_bool, save\n\nstate = msg['payload']\n\nif state != load_bool('telegram_enable'):\n save('telegram_enable', state)\n", "outputs": 1, - "x": 520, + "x": 540, "y": 3560, "wires": [ [] @@ -8663,19 +8631,261 @@ ] ] }, + { + "id": "48386fdb54980ec7", + "type": "comment", + "z": "e43a27722b508115", + "name": "Shield", + "info": "", + "x": 90, + "y": 3760, + "wires": [] + }, + { + "id": "fbc5fc2e65311f8b", + "type": "link in", + "z": "e43a27722b508115", + "name": "link in 3", + "links": [ + "50eeb3e362f9027f", + "960912e90ba5b5bc" + ], + "x": 65, + "y": 3840, + "wires": [ + [ + "5618e266f6966ae6" + ] + ] + }, + { + "id": "5618e266f6966ae6", + "type": "function", + "z": "e43a27722b508115", + "name": "loadl", + "func": "var file = 'shield_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 170, + "y": 3840, + "wires": [ + [ + "c97113d841391e40" + ] + ] + }, + { + "id": "c97113d841391e40", + "type": "ui_dropdown", + "z": "e43a27722b508115", + "name": "", + "label": "Shield Type", + "tooltip": "", + "place": "Select option", + "group": "0b244f698c7ac9a2", + "order": 0, + "width": 0, + "height": 0, + "passthru": true, + "multiple": false, + "options": [ + { + "label": "Green", + "value": "green", + "type": "str" + }, + { + "label": "Black", + "value": "black", + "type": "str" + } + ], + "payload": "", + "topic": "payload", + "topicType": "msg", + "className": "", + "x": 310, + "y": 3840, + "wires": [ + [ + "2b639346c1b56578" + ] + ] + }, + { + "id": "2b639346c1b56578", + "type": "function", + "z": "e43a27722b508115", + "name": "function 2", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'shield_type';\nconst current_shield = fs.readFileSync(filepath + file, 'utf8');\n\nvar current_choice = msg.payload\n\nif (current_choice != current_shield) {\n \n switch (current_choice) {\n case \"green\":\n fs.writeFile(filepath + 'pin_external', String(10), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(17), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(9), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(11), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(14), err => {\n if (err) {\n return\n }\n });\n break;\n case \"black\":\n fs.writeFile(filepath + 'pin_external', String(5), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight1', String(24), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_ringlight2', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_dir', String(23), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_step', String(27), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_enable', String(22), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_dir', String(6), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_tt_step', String(26), err => {\n if (err) {\n return\n }\n });\n fs.writeFile(filepath + 'pin_rotor_endstop', String(17), err => {\n if (err) {\n return\n }\n });\n break;\n case \"custom\":\n break;\n }\n\n fs.writeFile(filepath + file, current_choice, err => {\n if (err) {\n return\n }\n });\n}\n\nmsg.status = 'The new ' + current_choice + ' shield has been configured'\nmsg.topic = msg.status\nmsg.payload = 'Do you want to reboot now to apply the changes?'\n\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 460, + "y": 3840, + "wires": [ + [ + "137f032887544d74" + ] + ] + }, + { + "id": "137f032887544d74", + "type": "ui_toast", + "z": "e43a27722b508115", + "position": "prompt", + "displayTime": "3", + "highlight": "", + "sendall": false, + "outputs": 1, + "ok": "OK", + "cancel": "Cancel", + "raw": true, + "className": "", + "topic": "", + "name": "Reboot", + "x": 600, + "y": 3840, + "wires": [ + [ + "d2db49796fe0da79", + "d0d6820224b0ab0f" + ] + ] + }, + { + "id": "d2db49796fe0da79", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "", + "func": "print(msg['payload'])\nreturn msg", + "outputs": 1, + "x": 790, + "y": 3840, + "wires": [ + [] + ] + }, + { + "id": "d0d6820224b0ab0f", + "type": "debug", + "z": "e43a27722b508115", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 3720, + "wires": [] + }, + { + "id": "a74d78a1d186a833", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotate_tt_first'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1760, + "wires": [ + [ + "4e1b38e60f4179b0" + ] + ] + }, + { + "id": "4e1b38e60f4179b0", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotate_tt_first", + "label": "", + "tooltip": "", + "group": "38d121ea5b2bd77d", + "order": 3, + "width": "3", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 430, + "y": 1760, + "wires": [ + [ + "0e14711b77c43c23" + ] + ] + }, + { + "id": "0e14711b77c43c23", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotate_tt_first'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1760, + "wires": [ + [] + ] + }, + { + "id": "b1d13cc1ebec82d7", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "38d121ea5b2bd77d", + "order": 2, + "width": 3, + "height": 1, + "name": "rotate_tt_first_label", + "label": "Rotate turntable first", + "format": "", + "layout": "row-left", + "className": "", + "x": 790, + "y": 1760, + "wires": [] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", "z": "a5557543ccff5889", "name": "create beta new", - "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", + "func": "import json\nimport requests\nimport shutil\n\n#scope = 'main'\nscope = 'stable'\n\nupdatepath = '/home/pi/OpenScan/tmp/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-11S/update/2024-11S'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\n\n## load update.json\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'download update.json failed'\n return msg\n\nmsg = {}\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg2 = msg.copy()\nif scope in msg:\n del msg[scope]\n\nmsg[scope]={}\nmsg[scope]['1'] = {}\nmsg[scope]['1']['src'] = scope + '/fla.py'\nmsg[scope]['1']['dst'] = '/home/pi/OpenScan/files/fla.py'\n\nmsg[scope]['2'] = {}\nmsg[scope]['2']['src'] = scope + '/OpenScan.py'\nmsg[scope]['2']['dst'] = '/usr/lib/python3/dist-packages/OpenScan.py'\n\nmsg[scope]['3'] = {}\nmsg[scope]['3']['src'] = scope + '/config.txt'\nmsg[scope]['3']['dst'] = '/boot/config.txt'\n\nmsg[scope]['4'] = {}\nmsg[scope]['4']['src'] = scope + '/flows.json'\nmsg[scope]['4']['dst'] = '/home/pi/OpenScan/settings/.node-red/flows.json'\n\nmsg[scope]['5'] = {}\nmsg[scope]['5']['src'] = scope + '/settings.js'\nmsg[scope]['5']['dst'] = '/root/.node-red/settings.js'\n\n#msg[scope]['6'] = {}\n#msg[scope]['6']['src'] = 'files/logo.jpg'\n#msg[scope]['6']['dst'] = '/home/pi/OpenScan/files/logo.jpg'\n\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n filesize = os.path.getsize(filepath)\n msg[scope][i]['filesize'] = filesize\n\nif os.path.isdir('/home/pi/OpenScan/tmp/update/'):\n os.system('rm -r /home/pi/OpenScan/tmp/update') \nos.makedirs('/home/pi/OpenScan/tmp/update/')\n\nwith open('/home/pi/OpenScan/tmp/update/update.json', 'w+') as f:\n json.dump(msg, f, indent=4)\n\nfor i in msg[scope]:\n if not os.path.isdir(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])):\n os.makedirs(os.path.dirname('/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src']))\n shutil.copy(msg[scope][i]['dst'], '/home/pi/OpenScan/tmp/update/' + msg[scope][i]['src'])\n\nmsg['payload'] = 'created with scope: ' + scope\n\nreturn msg", "outputs": 1, "x": 260, "y": 140, "wires": [ - [ - "e23c514008cad1a1" - ] + [] ] }, { @@ -8763,29 +8973,12 @@ "y": 260, "wires": [] }, - { - "id": "e23c514008cad1a1", - "type": "debug", - "z": "a5557543ccff5889", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 480, - "y": 140, - "wires": [] - }, { "id": "b0629875a30ae1d7", "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\nopenscan_branch = load_str('openscan_branch')\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\n\nupdatepath = '/home/pi/OpenScan/updates/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + '/update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\nmsg['openscan_version'] = openscan_version\nmsg['openscan_branch'] = openscan_branch\nmsg['url'] = url\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -8820,7 +9013,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nopenscan_branch = msg['openscan_branch']\nopenscan_version = msg['openscan_version']\nurl = msg['url']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[openscan_branch]:\n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[openscan_branch][i]['filesize2'] = filesize\n if filesize == msg[openscan_branch][i]['filesize']:\n msg[openscan_branch][i]['update'] = False\n continue\n msg[openscan_branch][i]['update'] = True\n\n counter += 1\n\nmsg['openscan_branch'] = openscan_branch\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -8845,7 +9038,6 @@ "wires": [ [ "ec30638407332e43", - "38cbf7965d1c1834", "49f1ecb29a3f84f4" ] ] @@ -8855,14 +9047,14 @@ "type": "function", "z": "a5557543ccff5889", "name": "loadS", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "func": "var file = 'openscan_branch'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 210, - "y": 480, + "x": 370, + "y": 420, "wires": [ [ "2852023f3aa8db10" @@ -8904,8 +9096,8 @@ "topic": "topic", "topicType": "msg", "className": "", - "x": 340, - "y": 480, + "x": 500, + "y": 420, "wires": [ [ "1e10b387ee30c486" @@ -8917,14 +9109,14 @@ "type": "function", "z": "a5557543ccff5889", "name": "write", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'openscan_branch'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 470, - "y": 480, + "x": 630, + "y": 420, "wires": [ [] ] @@ -8938,12 +9130,12 @@ "width": 4, "height": 1, "name": "", - "label": "Updatetype: ", + "label": "Branch: ", "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 610, - "y": 480, + "x": 760, + "y": 420, "wires": [] }, { @@ -8972,33 +9164,14 @@ "offcolor": "", "animate": false, "className": "", - "x": 410, - "y": 440, + "x": 450, + "y": 480, "wires": [ [ "1ab4c6b4b232a022" ] ] }, - { - "id": "38cbf7965d1c1834", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 440, - "wires": [ - [ - "51cd8c8643e6b46a" - ] - ] - }, { "id": "1ab4c6b4b232a022", "type": "function", @@ -9010,8 +9183,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 440, + "x": 650, + "y": 480, "wires": [ [] ] @@ -9104,7 +9277,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/2024-12o/update/2024-12o/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = load_str('openscan_version')\n\nopenscan_branch = msg['openscan_branch']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = msg['url']\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[openscan_branch]:\n if msg[openscan_branch][i]['update'] == False:\n continue\n \n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + '/' + msg[openscan_branch][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[openscan_branch][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[openscan_branch][i]['dst'])\n \n if msg[openscan_branch][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, @@ -9152,17 +9325,18 @@ "type": "function", "z": "a5557543ccff5889", "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 210, - "y": 520, + "x": 217, + "y": 508, "wires": [ [ - "b0629875a30ae1d7" + "b0629875a30ae1d7", + "51cd8c8643e6b46a" ] ] }, @@ -9504,8 +9678,8 @@ "50eeb3e362f9027f", "960912e90ba5b5bc" ], - "x": 115, - "y": 420, + "x": 55, + "y": 120, "wires": [ [ "f128ca405d1e1e4d", @@ -9526,15 +9700,15 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 425, - "y": 407, + "x": 410, + "y": 100, "wires": [] }, { "id": "f128ca405d1e1e4d", "type": "exec", "z": "87715429b0b1c9a3", - "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "command": "cat /home/pi/OpenScan/statistics/statistics*.json|grep -i \\\"aborted\\\":false|wc -l", "addpay": "", "append": "", "useSpawn": "false", @@ -9542,8 +9716,8 @@ "winHide": false, "oldrc": false, "name": "Successful Scans", - "x": 250, - "y": 420, + "x": 210, + "y": 120, "wires": [ [ "cd0dc08fcb5968c8" @@ -9565,15 +9739,15 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 440, - "y": 340, + "x": 400, + "y": 160, "wires": [] }, { "id": "07d7ce3dab5f1c11", "type": "exec", "z": "87715429b0b1c9a3", - "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "command": "cat /home/pi/OpenScan/statistics/statistics*.json|grep -i \\\"aborted\\\":true|wc -l", "addpay": "", "append": "", "useSpawn": "false", @@ -9581,8 +9755,8 @@ "winHide": false, "oldrc": false, "name": "Aborted Scans", - "x": 245, - "y": 353, + "x": 200, + "y": 180, "wires": [ [ "b91b4d65f2090793" @@ -9590,5 +9764,15 @@ [], [] ] + }, + { + "id": "5b3aa9a71591ba34", + "type": "comment", + "z": "87715429b0b1c9a3", + "name": "Statistics", + "info": "", + "x": 100, + "y": 40, + "wires": [] } ] \ No newline at end of file diff --git a/update/2024-12o/meanwhile/flows.json b/update/2024-12o/meanwhile/flows.json index c8f8fa9..c68dde9 100644 --- a/update/2024-12o/meanwhile/flows.json +++ b/update/2024-12o/meanwhile/flows.json @@ -609,7 +609,7 @@ "type": "function", "z": "e6f4d02efb300ea9", "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'openscan_branch':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -2260,7 +2260,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics, ScanData\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"update_type\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nendstop_enable = load_bool('rotor_enable_endstop')\nif endstop_enable:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\n\nif counter == photocount:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n aborted=False\nelse:\n aborted=True\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nscan_data = ScanData(\n arch=architecture,\n openscan_version=openscan_version,\n openscan_branch=openscan_branch,\n shield=shield,\n date_init=date_init,\n date_end=date_end,\n num_photos=photocount,\n done_photos=counter,\n camera=camera_model,\n stack_size=stacksize,\n telegram_enabled=telegram_enable,\n delete_aborted=delete_aborted,\n endstop_enabled=endstop_enable,\n group_stack_photos=group_stack_photos,\n aborted=aborted\n)\n\nstats.write_statistics(scan_data)\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics, ScanData\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"openscan_branch\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nendstop_enable = load_bool('rotor_enable_endstop')\nif endstop_enable:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\n\nif counter == photocount:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n aborted=False\nelse:\n aborted=True\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nscan_data = ScanData(\n arch=architecture,\n openscan_version=openscan_version,\n openscan_branch=openscan_branch,\n shield=shield,\n date_init=date_init,\n date_end=date_end,\n num_photos=photocount,\n done_photos=counter,\n camera=camera_model,\n stack_size=stacksize,\n telegram_enabled=telegram_enable,\n delete_aborted=delete_aborted,\n endstop_enabled=endstop_enable,\n group_stack_photos=group_stack_photos,\n aborted=aborted\n)\n\nstats.write_statistics(scan_data)\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -6275,9 +6275,9 @@ "type": "function", "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nvar steps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", "outputs": 1, - "noerr": 4, + "noerr": 0, "initialize": "", "finalize": "", "libs": [], @@ -7069,9 +7069,9 @@ "type": "function", "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "noerr": 7, + "noerr": 0, "initialize": "", "finalize": "", "libs": [], @@ -9015,7 +9015,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\nopenscan_branch = load_str('openscan_branch')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + '/update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\nmsg['url'] = url\nmsg['openscan_branch'] = openscan_branch\nmsg['openscan_version'] = openscan_version\n\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -9050,7 +9050,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nopenscan_version = msg['openscan_version']\nopenscan_branch = msg['openscan_branch']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = msg['url']\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[openscan_branch]:\n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[openscan_branch][i]['filesize2'] = filesize\n if filesize == msg[openscan_branch][i]['filesize']:\n msg[openscan_branch][i]['update'] = False\n continue\n msg[openscan_branch][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -9075,7 +9075,6 @@ "wires": [ [ "ec30638407332e43", - "38cbf7965d1c1834", "49f1ecb29a3f84f4" ] ] @@ -9085,14 +9084,14 @@ "type": "function", "z": "a5557543ccff5889", "name": "loadS", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "func": "var file = 'openscan_branch'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 210, - "y": 480, + "y": 440, "wires": [ [ "2852023f3aa8db10" @@ -9134,8 +9133,8 @@ "topic": "topic", "topicType": "msg", "className": "", - "x": 340, - "y": 480, + "x": 390, + "y": 440, "wires": [ [ "1e10b387ee30c486" @@ -9147,14 +9146,14 @@ "type": "function", "z": "a5557543ccff5889", "name": "write", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'openscan_branch'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 470, - "y": 480, + "x": 520, + "y": 440, "wires": [ [] ] @@ -9168,12 +9167,12 @@ "width": 4, "height": 1, "name": "", - "label": "Updatetype: ", + "label": "Branch: ", "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 610, - "y": 480, + "x": 650, + "y": 440, "wires": [] }, { @@ -9202,33 +9201,14 @@ "offcolor": "", "animate": false, "className": "", - "x": 410, - "y": 440, + "x": 449, + "y": 491, "wires": [ [ "1ab4c6b4b232a022" ] ] }, - { - "id": "38cbf7965d1c1834", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 440, - "wires": [ - [ - "51cd8c8643e6b46a" - ] - ] - }, { "id": "1ab4c6b4b232a022", "type": "function", @@ -9240,8 +9220,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 440, + "x": 649, + "y": 491, "wires": [ [] ] @@ -9334,7 +9314,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = load_str('openscan_version')\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = msg['openscan_version']\nopenscan_branch = msg['openscan_branch']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = msg['url']\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[openscan_branch]:\n if msg[openscan_branch][i]['update'] == False:\n continue\n \n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + '/' + msg[openscan_branch][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[openscan_branch][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[openscan_branch][i]['dst'])\n \n if msg[openscan_branch][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, @@ -9389,10 +9369,11 @@ "finalize": "", "libs": [], "x": 210, - "y": 520, + "y": 480, "wires": [ [ - "b0629875a30ae1d7" + "b0629875a30ae1d7", + "51cd8c8643e6b46a" ] ] }, diff --git a/update/2024-12o/stable/OpenScan.py b/update/2024-12o/stable/OpenScan.py index e634511..cbce61c 100644 --- a/update/2024-12o/stable/OpenScan.py +++ b/update/2024-12o/stable/OpenScan.py @@ -148,13 +148,18 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): dir = load_int(motor + '_dir') ramp = load_int(motor + '_accramp') acc = load_float(motor + '_acc') + if motor != 'tt': + ES_pin = load_int('pin_' + motor + '_endstop') + else: + ES_pin = '33' delay_init = load_float(motor + '_delay') delay = delay_init step_count=int(angle*spr/360) * dir GPIO.setup(dirpin, GPIO.OUT) GPIO.setup(steppin, GPIO.OUT) - GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + if motor != 'tt': + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) if (step_count>0): GPIO.output(dirpin, GPIO.HIGH) @@ -162,7 +167,7 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): GPIO.output(dirpin, GPIO.LOW) step_count=-step_count for x in range(step_count): - if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: + if ES_enable == True and GPIO.input(ES_pin) != ES_start_state and motor != 'tt': i = 0 while i <= 10: if GPIO.input(ES_pin) == ES_start_state: diff --git a/update/2024-12o/stable/OpenScanStatistics.py b/update/2024-12o/stable/OpenScanStatistics.py index 68005af..8adf9dd 100755 --- a/update/2024-12o/stable/OpenScanStatistics.py +++ b/update/2024-12o/stable/OpenScanStatistics.py @@ -1,18 +1,38 @@ -import csv +import json +import os +from datetime import datetime +from dataclasses import dataclass + +@dataclass +class ScanData: + arch: str + openscan_version: str + openscan_branch: str + shield: str + date_init: str # Format: YYYY-MM-DD HH:MM + date_end: str # Format: YYYY-MM-DD HH:MM + num_photos: int + done_photos: int + camera: str + stack_size: int + telegram_enabled: bool + delete_aborted: bool + endstop_enabled: bool + group_stack_photos: bool + aborted: bool class ScanStatistics: - def __init__(self, filename="/home/pi/OpenScan/statistics/statistics.csv"): - self.filename = filename - self.header = ["arch", "shield", "date_init", "date_end", "num_photos", "done-photos", "camera", "aborted"] + def __init__(self, filename: str = "/home/pi/OpenScan/statistics") -> None: + self.filename: str = filename + + def write_statistics(self, scan_data: ScanData) -> None: + data: dict = scan_data.__dict__ # Convert dataclass to dictionary + + # Parse date_init to get year and month + date_object: datetime = datetime.strptime(scan_data.date_init, "%Y-%m-%d %H:%M") + record_filename: str = os.path.join(self.filename, f"statistics-{date_object.year}-{date_object.month:02d}.json") - def write_statistics(self, arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted): - data = [arch, shield, date_init, date_end, num_photos, done_photos, camera, aborted] - - with open(self.filename, "a", newline='') as csv_file: - csv_writer = csv.writer(csv_file, delimiter=';') - - # Write header if file is empty - if csv_file.tell() == 0: - csv_writer.writerow(self.header) - - csv_writer.writerow(data) + # Append the new data as a new line + with open(record_filename, "a") as json_file: + json.dump(data, json_file, separators=(',', ':'), indent=None) # Collapsed JSON + json_file.write('\n') # Add a newline after each entry \ No newline at end of file diff --git a/update/2024-12o/stable/flows.json b/update/2024-12o/stable/flows.json index 9ba0c97..e1f0cd3 100644 --- a/update/2024-12o/stable/flows.json +++ b/update/2024-12o/stable/flows.json @@ -246,17 +246,6 @@ "collapse": false, "className": "" }, - { - "id": "5b3e5aca21140e9a", - "type": "ui_group", - "name": "Update", - "tab": "b3150b13e34b1fe8", - "order": 1, - "disp": false, - "width": "6", - "collapse": false, - "className": "" - }, { "id": "b3150b13e34b1fe8", "type": "ui_tab", @@ -332,7 +321,7 @@ { "id": "7a3279eea439bcdd", "type": "ui_group", - "name": "Motor", + "name": "Rotor", "tab": "457102eadc9ddb6c", "order": 7, "disp": true, @@ -471,16 +460,6 @@ "width": 4, "height": 1 }, - { - "id": "0799b02d12fc3a14", - "type": "ui_spacer", - "z": "e43a27722b508115", - "name": "spacer", - "group": "7a3279eea439bcdd", - "order": 25, - "width": 6, - "height": 1 - }, { "id": "220493325bb79987", "type": "ui_group", @@ -524,36 +503,25 @@ "className": "" }, { - "id": "e357ef02.ef3cb", + "id": "f622137daacdfebe", "type": "ui_group", - "name": "Page 1", - "tab": "4bc17c6e.b74934", - "order": 2, - "disp": false, - "width": "6", - "collapse": false, - "className": "" + "name": "Group 3", + "tab": "b3150b13e34b1fe8", + "order": 3, + "disp": true, + "width": 6 }, { - "id": "c9165343995892c6", + "id": "38d121ea5b2bd77d", "type": "ui_group", - "name": "Page 2", - "tab": "", - "order": 1, - "disp": false, + "name": "Turntable", + "tab": "457102eadc9ddb6c", + "order": 9, + "disp": true, "width": "6", "collapse": false, "className": "" }, - { - "id": "4bc17c6e.b74934", - "type": "ui_tab", - "name": "Page 1", - "icon": "wi-wu-tstorms", - "order": 5, - "disabled": false, - "hidden": false - }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -641,7 +609,7 @@ "type": "function", "z": "e6f4d02efb300ea9", "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'update_type':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'openscan_branch':'stable',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -1057,7 +1025,7 @@ "type": "ui_button", "z": "e6f4d02efb300ea9", "name": "", - "group": "5b3e5aca21140e9a", + "group": "", "order": 1, "width": 6, "height": 3, @@ -1295,23 +1263,6 @@ ] ] }, - { - "id": "fa6db57803ae2b6d", - "type": "debug", - "z": "e6f4d02efb300ea9", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": true, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 590, - "y": 460, - "wires": [] - }, { "id": "b1b0ccb783dd5882", "type": "change", @@ -1335,7 +1286,6 @@ "y": 360, "wires": [ [ - "fa6db57803ae2b6d", "0d8c6bc7887fb3c2" ] ] @@ -2292,7 +2242,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nif load_bool('rotor_enable_endstop'):\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\nif counter == photocount:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, False)\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\nelse:\n stats.write_statistics(architecture, shield, date_init, date_end, photocount, counter, camera_model, True)\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics, ScanData\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"openscan_branch\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nendstop_enable = load_bool('rotor_enable_endstop')\nif endstop_enable:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\n\nif counter == photocount:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n aborted=False\nelse:\n aborted=True\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nscan_data = ScanData(\n arch=architecture,\n openscan_version=openscan_version,\n openscan_branch=openscan_branch,\n shield=shield,\n date_init=date_init,\n date_end=date_end,\n num_photos=photocount,\n done_photos=counter,\n camera=camera_model,\n stack_size=stacksize,\n telegram_enabled=telegram_enable,\n delete_aborted=delete_aborted,\n endstop_enabled=endstop_enable,\n group_stack_photos=group_stack_photos,\n aborted=aborted\n)\n\nstats.write_statistics(scan_data)\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -2806,8 +2756,8 @@ "complete": "false", "statusVal": "", "statusType": "auto", - "x": 620, - "y": 1000, + "x": 560, + "y": 960, "wires": [] }, { @@ -4840,8 +4790,8 @@ "name": "tt delay", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 30, + "group": "38d121ea5b2bd77d", + "order": 7, "width": 3, "height": 1, "passthru": false, @@ -4945,10 +4895,10 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 19, + "order": 17, "width": 3, "height": 1, - "name": "rotor Accramp", + "name": "rotor_acc_label", "label": "Acceleration ramp", "format": "", "layout": "row-left", @@ -4962,15 +4912,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 13, + "order": 19, "width": 3, "height": 1, - "name": "rotor_Steps per Rotation", + "name": "rotor_accramp_label", "label": "Steps per Rotation", "format": "", "layout": "row-spread", "className": "", - "x": 810, + "x": 800, "y": 2180, "wires": [] }, @@ -4979,15 +4929,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 17, + "order": 15, "width": 3, "height": 1, - "name": "rotor Acc", + "name": "rotor_delay_label", "label": "Acceleration", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 790, "y": 2100, "wires": [] }, @@ -4996,15 +4946,15 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 15, + "order": 13, "width": 3, "height": 1, - "name": "rotor_delay", + "name": "rotor_steps_rotation_label", "label": "Delay", "format": "", "layout": "row-left", "className": "", - "x": 770, + "x": 810, "y": 2060, "wires": [] }, @@ -5012,8 +4962,8 @@ "id": "355e89ab4e5484e4", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 26, + "group": "38d121ea5b2bd77d", + "order": 1, "width": 6, "height": 1, "name": "tt", @@ -5032,8 +4982,8 @@ "name": "tt_acc", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 32, + "group": "38d121ea5b2bd77d", + "order": 9, "width": 3, "height": 1, "passthru": false, @@ -5059,8 +5009,8 @@ "name": "tt_accramp", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 34, + "group": "38d121ea5b2bd77d", + "order": 11, "width": 3, "height": 1, "passthru": false, @@ -5086,8 +5036,8 @@ "name": "tt_stepsperrotation", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 28, + "group": "38d121ea5b2bd77d", + "order": 5, "width": 3, "height": 1, "passthru": false, @@ -5109,16 +5059,16 @@ "id": "18e5918748660109", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 33, + "group": "38d121ea5b2bd77d", + "order": 10, "width": 3, "height": 1, - "name": "ttAccramp", + "name": "tt_accramp_label", "label": "Acceleration ramp", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 780, "y": 2420, "wires": [] }, @@ -5126,16 +5076,16 @@ "id": "8e805244dc1899e8", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 27, + "group": "38d121ea5b2bd77d", + "order": 4, "width": 3, "height": 1, - "name": "tt_steps per Rotation", + "name": "tt_stepsperrotation_label", "label": "Steps per Rotation", "format": "", "layout": "row-spread", "className": "", - "x": 800, + "x": 810, "y": 2300, "wires": [] }, @@ -5143,16 +5093,16 @@ "id": "a09e5fbea861bfb1", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 31, + "group": "38d121ea5b2bd77d", + "order": 8, "width": 3, "height": 1, - "name": "tt Acc", + "name": "tt_acc_label", "label": "Acceleration", "format": "", "layout": "row-left", "className": "", - "x": 750, + "x": 770, "y": 2380, "wires": [] }, @@ -5160,16 +5110,16 @@ "id": "7b06448b3b222011", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 29, + "group": "38d121ea5b2bd77d", + "order": 6, "width": 3, "height": 1, - "name": "tt_delay", + "name": "tt_delay_label", "label": "Delay", "format": "", "layout": "row-left", "className": "", - "x": 760, + "x": 780, "y": 2340, "wires": [] }, @@ -5208,12 +5158,12 @@ "order": 21, "width": 3, "height": 1, - "name": "rotor_angle", + "name": "rotor_angle_label", "label": "Manual angle", "format": "", "layout": "row-spread", "className": "", - "x": 770, + "x": 790, "y": 2220, "wires": [] }, @@ -5224,8 +5174,8 @@ "name": "tt_angle", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 36, + "group": "38d121ea5b2bd77d", + "order": 13, "width": 3, "height": 1, "passthru": false, @@ -5248,16 +5198,16 @@ "id": "96a9febc0928b6f0", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 35, + "group": "38d121ea5b2bd77d", + "order": 12, "width": 3, "height": 1, - "name": "tt_angle", + "name": "tt_angle_label", "label": "Manual angle", "format": "", "layout": "row-spread", "className": "", - "x": 760, + "x": 780, "y": 2460, "wires": [] }, @@ -5285,8 +5235,8 @@ "name": "tt_dir", "label": "", "tooltip": "", - "group": "7a3279eea439bcdd", - "order": 38, + "group": "38d121ea5b2bd77d", + "order": 15, "width": 3, "height": 1, "passthru": false, @@ -5336,16 +5286,16 @@ "id": "6b0d58943ecb8bb2", "type": "ui_text", "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 37, + "group": "38d121ea5b2bd77d", + "order": 14, "width": 3, "height": 1, - "name": "tt_dir", + "name": "tt_dir_label", "label": "Direction", "format": "", "layout": "row-spread", "className": "", - "x": 750, + "x": 770, "y": 2500, "wires": [] }, @@ -5357,12 +5307,12 @@ "order": 23, "width": 3, "height": 1, - "name": "rotor_dir", + "name": "rotor_dir_label", "label": "Direction", "format": "", "layout": "row-spread", "className": "", - "x": 760, + "x": 780, "y": 2260, "wires": [] }, @@ -5414,7 +5364,7 @@ "max": "1", "step": "0.02", "className": "", - "x": 430, + "x": 450, "y": 2600, "wires": [ [ @@ -5441,7 +5391,7 @@ "max": "10", "step": "0.1", "className": "", - "x": 400, + "x": 420, "y": 2640, "wires": [ [ @@ -6307,9 +6257,9 @@ "type": "function", "z": "e43a27722b508115", "name": "loadI", - "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nsteps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", + "func": "var file = 'tt_stepsperrotation'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nvar steps = parseInt(data);\nif (steps == 3600){\n steps = 3200\n}\n\nmsg.payload = steps\n\nreturn msg", "outputs": 1, - "noerr": 4, + "noerr": 0, "initialize": "", "finalize": "", "libs": [], @@ -7101,9 +7051,9 @@ "type": "function", "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'advanced_settings'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, - "noerr": 7, + "noerr": 0, "initialize": "", "finalize": "", "libs": [], @@ -7197,12 +7147,12 @@ "order": 5, "width": 3, "height": 1, - "name": "rotor_anglemin", + "name": "rotor_anglemin_label", "label": "Min Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1820, "wires": [] }, @@ -7384,12 +7334,12 @@ "order": 7, "width": 3, "height": 1, - "name": "rotor_anglemax", + "name": "rotor_anglemax_label", "label": "Max Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1860, "wires": [] }, @@ -7401,12 +7351,12 @@ "order": 3, "width": 3, "height": 1, - "name": "rotor_anglestart", + "name": "rotor_anglestart_label", "label": "Start Angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 800, "y": 1900, "wires": [] }, @@ -7480,7 +7430,8 @@ "5b02160c33605ae7", "304c135ec09801e3", "f036424d79645761", - "b7db72b7f0599ebd" + "b7db72b7f0599ebd", + "a74d78a1d186a833" ] ] }, @@ -8148,12 +8099,12 @@ "order": 11, "width": 3, "height": 1, - "name": "endstop_angle", + "name": "rotor_endstop_angle_label", "label": "Endstop angle", "format": "", "layout": "row-left", "className": "", - "x": 780, + "x": 820, "y": 2020, "wires": [] }, @@ -8272,12 +8223,12 @@ "order": 9, "width": 3, "height": 1, - "name": "rotor_enable_endstop", + "name": "rotor_enable_endstop_label", "label": "Enable Endstop", "format": "", "layout": "row-left", "className": "", - "x": 800, + "x": 820, "y": 1940, "wires": [] }, @@ -8837,6 +8788,93 @@ "y": 3720, "wires": [] }, + { + "id": "a74d78a1d186a833", + "type": "function", + "z": "e43a27722b508115", + "name": "loadB", + "func": "var file = 'rotate_tt_first'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath + file, 'utf8');\nif (data === '1' || data === 'True' || data === 'true') {\n data = true;\n}\nelse {\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 1760, + "wires": [ + [ + "4e1b38e60f4179b0" + ] + ] + }, + { + "id": "4e1b38e60f4179b0", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotate_tt_first", + "label": "", + "tooltip": "", + "group": "38d121ea5b2bd77d", + "order": 3, + "width": "3", + "height": "1", + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 430, + "y": 1760, + "wires": [ + [ + "0e14711b77c43c23" + ] + ] + }, + { + "id": "0e14711b77c43c23", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotate_tt_first'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 1760, + "wires": [ + [] + ] + }, + { + "id": "b1d13cc1ebec82d7", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "38d121ea5b2bd77d", + "order": 2, + "width": 3, + "height": 1, + "name": "rotate_tt_first_label", + "label": "Rotate turntable first", + "format": "", + "layout": "row-left", + "className": "", + "x": 790, + "y": 1760, + "wires": [] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", @@ -8847,9 +8885,7 @@ "x": 260, "y": 140, "wires": [ - [ - "e23c514008cad1a1" - ] + [] ] }, { @@ -8937,29 +8973,12 @@ "y": 260, "wires": [] }, - { - "id": "e23c514008cad1a1", - "type": "debug", - "z": "a5557543ccff5889", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 480, - "y": 140, - "wires": [] - }, { "id": "b0629875a30ae1d7", "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + 'update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\n\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\nopenscan_branch = load_str('openscan_branch')\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\n\nupdatepath = '/home/pi/OpenScan/updates/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + '/update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\nmsg['openscan_version'] = openscan_version\nmsg['openscan_branch'] = openscan_branch\nmsg['url'] = url\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -8994,7 +9013,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nscope = load_str('update_type')\nmsg['scope'] = scope\n\nopenscan_version = load_str('openscan_version')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[scope]:\n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[scope][i]['filesize2'] = filesize\n if filesize == msg[scope][i]['filesize']:\n msg[scope][i]['update'] = False\n continue\n msg[scope][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nopenscan_branch = msg['openscan_branch']\nopenscan_version = msg['openscan_version']\nurl = msg['url']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[openscan_branch]:\n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[openscan_branch][i]['filesize2'] = filesize\n if filesize == msg[openscan_branch][i]['filesize']:\n msg[openscan_branch][i]['update'] = False\n continue\n msg[openscan_branch][i]['update'] = True\n\n counter += 1\n\nmsg['openscan_branch'] = openscan_branch\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -9019,7 +9038,6 @@ "wires": [ [ "ec30638407332e43", - "38cbf7965d1c1834", "49f1ecb29a3f84f4" ] ] @@ -9029,14 +9047,14 @@ "type": "function", "z": "a5557543ccff5889", "name": "loadS", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", + "func": "var file = 'openscan_branch'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = String(data.replace(/(\\r\\n|\\n|\\r)/gm,\"\"));\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 210, - "y": 480, + "x": 370, + "y": 420, "wires": [ [ "2852023f3aa8db10" @@ -9078,8 +9096,8 @@ "topic": "topic", "topicType": "msg", "className": "", - "x": 340, - "y": 480, + "x": 500, + "y": 420, "wires": [ [ "1e10b387ee30c486" @@ -9091,14 +9109,14 @@ "type": "function", "z": "a5557543ccff5889", "name": "write", - "func": "var file = 'update_type'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'openscan_branch'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 470, - "y": 480, + "x": 630, + "y": 420, "wires": [ [] ] @@ -9112,12 +9130,12 @@ "width": 4, "height": 1, "name": "", - "label": "Updatetype: ", + "label": "Branch: ", "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 610, - "y": 480, + "x": 760, + "y": 420, "wires": [] }, { @@ -9146,33 +9164,14 @@ "offcolor": "", "animate": false, "className": "", - "x": 410, - "y": 440, + "x": 450, + "y": 480, "wires": [ [ "1ab4c6b4b232a022" ] ] }, - { - "id": "38cbf7965d1c1834", - "type": "function", - "z": "a5557543ccff5889", - "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 210, - "y": 440, - "wires": [ - [ - "51cd8c8643e6b46a" - ] - ] - }, { "id": "1ab4c6b4b232a022", "type": "function", @@ -9184,8 +9183,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 610, - "y": 440, + "x": 650, + "y": 480, "wires": [ [] ] @@ -9278,7 +9277,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = load_str('openscan_version')\n\nscope = msg['scope']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version + '/'\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[scope]:\n if msg[scope][i]['update'] == False:\n continue\n \n filepath = msg[scope][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + msg[scope][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[scope][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[scope][i]['dst'])\n \n if msg[scope][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = load_str('openscan_version')\n\nopenscan_branch = msg['openscan_branch']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = msg['url']\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[openscan_branch]:\n if msg[openscan_branch][i]['update'] == False:\n continue\n \n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + '/' + msg[openscan_branch][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[openscan_branch][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[openscan_branch][i]['dst'])\n \n if msg[openscan_branch][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, @@ -9326,17 +9325,18 @@ "type": "function", "z": "a5557543ccff5889", "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 210, - "y": 520, + "x": 217, + "y": 508, "wires": [ [ - "b0629875a30ae1d7" + "b0629875a30ae1d7", + "51cd8c8643e6b46a" ] ] }, @@ -9700,15 +9700,15 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 450, - "y": 180, + "x": 410, + "y": 100, "wires": [] }, { "id": "f128ca405d1e1e4d", "type": "exec", "z": "87715429b0b1c9a3", - "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi false|tail -n +2|wc -l", + "command": "cat /home/pi/OpenScan/statistics/statistics*.json|grep -i \\\"aborted\\\":false|wc -l", "addpay": "", "append": "", "useSpawn": "false", @@ -9717,7 +9717,7 @@ "oldrc": false, "name": "Successful Scans", "x": 210, - "y": 180, + "y": 120, "wires": [ [ "cd0dc08fcb5968c8" @@ -9739,15 +9739,15 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 440, - "y": 120, + "x": 400, + "y": 160, "wires": [] }, { "id": "07d7ce3dab5f1c11", "type": "exec", "z": "87715429b0b1c9a3", - "command": "cat /home/pi/OpenScan/statistics/statistics.csv|grep -vi True|tail -n +2|wc -l", + "command": "cat /home/pi/OpenScan/statistics/statistics*.json|grep -i \\\"aborted\\\":true|wc -l", "addpay": "", "append": "", "useSpawn": "false", @@ -9756,7 +9756,7 @@ "oldrc": false, "name": "Aborted Scans", "x": 200, - "y": 120, + "y": 180, "wires": [ [ "b91b4d65f2090793" diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json index b000458..d2098e0 100644 --- a/update/2024-12o/update.json +++ b/update/2024-12o/update.json @@ -1,37 +1,37 @@ { "stable": { "1": { - "src": "meanwhile/OpenScan.py", + "src": "stable/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10249 + "filesize": 10396 }, "2": { - "src": "meanwhile/OpenScanStatistics.py", + "src": "stable/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", - "filesize": 793 + "filesize": 1288 }, "3": { - "src": "meanwhile/config.txt", + "src": "stable/config.txt", "dst": "/boot/config.txt", "filesize": 864 }, "4": { - "src": "meanwhile/expand_root.sh", - "dst": "/home/pi/OpensScan/files/expand_root.sh", + "src": "stable/expand_root.sh", + "dst": "/home/pi/OpenScan/files/expand_root.sh", "filesize": 170 }, "5": { - "src": "meanwhile/fla.py", + "src": "stable/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", "filesize": 17869 }, "6": { - "src": "meanwhile/flows.json", + "src": "stable/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 341588 + "filesize": 342711 }, "7": { - "src": "meanwhile/settings.js", + "src": "stable/settings.js", "dst": "/root/.node-red/settings.js", "filesize": 21248 } @@ -39,23 +39,23 @@ "beta": { "1": { - "src": "meanwhile/OpenScan.py", + "src": "beta/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10249 + "filesize": 10396 }, "2": { - "src": "meanwhile/OpenScanStatistics.py", + "src": "beta/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", - "filesize": 793 + "filesize": 1288 }, "3": { - "src": "meanwhile/config.txt", + "src": "beta/config.txt", "dst": "/boot/config.txt", "filesize": 864 }, "4": { - "src": "meanwhile/expand_root.sh", - "dst": "/home/pi/OpensScan/files/expand_root.sh", + "src": "beta/expand_root.sh", + "dst": "/home/pi/OpenScan/files/expand_root.sh", "filesize": 170 }, "5": { @@ -64,12 +64,12 @@ "filesize": 17869 }, "6": { - "src": "meanwhile/flows.json", + "src": "beta/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 333640 + "filesize": 342709 }, "7": { - "src": "meanwhile/settings.js", + "src": "beta/settings.js", "dst": "/root/.node-red/settings.js", "filesize": 21248 } @@ -92,7 +92,7 @@ }, "4": { "src": "meanwhile/expand_root.sh", - "dst": "/home/pi/OpensScan/files/expand_root.sh", + "dst": "/home/pi/OpenScan/files/expand_root.sh", "filesize": 170 }, "5": { @@ -103,7 +103,7 @@ "6": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 344141 + "filesize": 343552 }, "7": { "src": "meanwhile/settings.js", From aad761bb7f461bd12b4033212095019808beba61 Mon Sep 17 00:00:00 2001 From: Stealth Date: Tue, 1 Oct 2024 11:25:46 +0200 Subject: [PATCH 24/38] rename branch --- update/2024-12o/stable/flows.json | 2 +- update/2024-12o/update.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/update/2024-12o/stable/flows.json b/update/2024-12o/stable/flows.json index e1f0cd3..d46d3d0 100644 --- a/update/2024-12o/stable/flows.json +++ b/update/2024-12o/stable/flows.json @@ -871,7 +871,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, diff --git a/update/2024-12o/update.json b/update/2024-12o/update.json index d2098e0..431b3c5 100644 --- a/update/2024-12o/update.json +++ b/update/2024-12o/update.json @@ -28,7 +28,7 @@ "6": { "src": "stable/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 342711 + "filesize": 342710 }, "7": { "src": "stable/settings.js", From c205fbf45d13be37c37fa8e025eace527dc818e9 Mon Sep 17 00:00:00 2001 From: Stealth Date: Tue, 1 Oct 2024 11:30:45 +0200 Subject: [PATCH 25/38] rename branch --- update/{2024-12o => 2024-1o}/beta/OpenScan.py | 0 update/{2024-12o => 2024-1o}/beta/OpenScanStatistics.py | 0 update/{2024-12o => 2024-1o}/beta/config.txt | 0 update/{2024-12o => 2024-1o}/beta/fla.py | 0 update/{2024-12o => 2024-1o}/beta/flows.json | 0 update/{2024-12o => 2024-1o}/beta/settings.js | 0 update/{2024-12o => 2024-1o}/meanwhile/OpenScan.py | 0 update/{2024-12o => 2024-1o}/meanwhile/OpenScanStatistics.py | 0 update/{2024-12o => 2024-1o}/meanwhile/config.txt | 0 update/{2024-12o => 2024-1o}/meanwhile/fla.py | 0 update/{2024-12o => 2024-1o}/meanwhile/flows.json | 2 +- update/{2024-12o => 2024-1o}/meanwhile/settings.js | 0 update/{2024-12o => 2024-1o}/scripts/expand_root.sh | 0 update/{2024-12o => 2024-1o}/scripts/startup.sh | 0 update/{2024-12o => 2024-1o}/stable/OpenScan.py | 0 update/{2024-12o => 2024-1o}/stable/OpenScanStatistics.py | 0 update/{2024-12o => 2024-1o}/stable/config.txt | 0 update/{2024-12o => 2024-1o}/stable/fla.py | 0 update/{2024-12o => 2024-1o}/stable/flows.json | 0 update/{2024-12o => 2024-1o}/stable/settings.js | 0 update/{2024-12o => 2024-1o}/update.json | 2 +- 21 files changed, 2 insertions(+), 2 deletions(-) rename update/{2024-12o => 2024-1o}/beta/OpenScan.py (100%) rename update/{2024-12o => 2024-1o}/beta/OpenScanStatistics.py (100%) rename update/{2024-12o => 2024-1o}/beta/config.txt (100%) rename update/{2024-12o => 2024-1o}/beta/fla.py (100%) rename update/{2024-12o => 2024-1o}/beta/flows.json (100%) rename update/{2024-12o => 2024-1o}/beta/settings.js (100%) rename update/{2024-12o => 2024-1o}/meanwhile/OpenScan.py (100%) rename update/{2024-12o => 2024-1o}/meanwhile/OpenScanStatistics.py (100%) rename update/{2024-12o => 2024-1o}/meanwhile/config.txt (100%) rename update/{2024-12o => 2024-1o}/meanwhile/fla.py (100%) rename update/{2024-12o => 2024-1o}/meanwhile/flows.json (99%) rename update/{2024-12o => 2024-1o}/meanwhile/settings.js (100%) rename update/{2024-12o => 2024-1o}/scripts/expand_root.sh (100%) rename update/{2024-12o => 2024-1o}/scripts/startup.sh (100%) rename update/{2024-12o => 2024-1o}/stable/OpenScan.py (100%) rename update/{2024-12o => 2024-1o}/stable/OpenScanStatistics.py (100%) rename update/{2024-12o => 2024-1o}/stable/config.txt (100%) rename update/{2024-12o => 2024-1o}/stable/fla.py (100%) rename update/{2024-12o => 2024-1o}/stable/flows.json (100%) rename update/{2024-12o => 2024-1o}/stable/settings.js (100%) rename update/{2024-12o => 2024-1o}/update.json (99%) diff --git a/update/2024-12o/beta/OpenScan.py b/update/2024-1o/beta/OpenScan.py similarity index 100% rename from update/2024-12o/beta/OpenScan.py rename to update/2024-1o/beta/OpenScan.py diff --git a/update/2024-12o/beta/OpenScanStatistics.py b/update/2024-1o/beta/OpenScanStatistics.py similarity index 100% rename from update/2024-12o/beta/OpenScanStatistics.py rename to update/2024-1o/beta/OpenScanStatistics.py diff --git a/update/2024-12o/beta/config.txt b/update/2024-1o/beta/config.txt similarity index 100% rename from update/2024-12o/beta/config.txt rename to update/2024-1o/beta/config.txt diff --git a/update/2024-12o/beta/fla.py b/update/2024-1o/beta/fla.py similarity index 100% rename from update/2024-12o/beta/fla.py rename to update/2024-1o/beta/fla.py diff --git a/update/2024-12o/beta/flows.json b/update/2024-1o/beta/flows.json similarity index 100% rename from update/2024-12o/beta/flows.json rename to update/2024-1o/beta/flows.json diff --git a/update/2024-12o/beta/settings.js b/update/2024-1o/beta/settings.js similarity index 100% rename from update/2024-12o/beta/settings.js rename to update/2024-1o/beta/settings.js diff --git a/update/2024-12o/meanwhile/OpenScan.py b/update/2024-1o/meanwhile/OpenScan.py similarity index 100% rename from update/2024-12o/meanwhile/OpenScan.py rename to update/2024-1o/meanwhile/OpenScan.py diff --git a/update/2024-12o/meanwhile/OpenScanStatistics.py b/update/2024-1o/meanwhile/OpenScanStatistics.py similarity index 100% rename from update/2024-12o/meanwhile/OpenScanStatistics.py rename to update/2024-1o/meanwhile/OpenScanStatistics.py diff --git a/update/2024-12o/meanwhile/config.txt b/update/2024-1o/meanwhile/config.txt similarity index 100% rename from update/2024-12o/meanwhile/config.txt rename to update/2024-1o/meanwhile/config.txt diff --git a/update/2024-12o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py similarity index 100% rename from update/2024-12o/meanwhile/fla.py rename to update/2024-1o/meanwhile/fla.py diff --git a/update/2024-12o/meanwhile/flows.json b/update/2024-1o/meanwhile/flows.json similarity index 99% rename from update/2024-12o/meanwhile/flows.json rename to update/2024-1o/meanwhile/flows.json index c68dde9..f248be5 100644 --- a/update/2024-12o/meanwhile/flows.json +++ b/update/2024-1o/meanwhile/flows.json @@ -871,7 +871,7 @@ "order": 14, "width": 7, "height": 1, - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, diff --git a/update/2024-12o/meanwhile/settings.js b/update/2024-1o/meanwhile/settings.js similarity index 100% rename from update/2024-12o/meanwhile/settings.js rename to update/2024-1o/meanwhile/settings.js diff --git a/update/2024-12o/scripts/expand_root.sh b/update/2024-1o/scripts/expand_root.sh similarity index 100% rename from update/2024-12o/scripts/expand_root.sh rename to update/2024-1o/scripts/expand_root.sh diff --git a/update/2024-12o/scripts/startup.sh b/update/2024-1o/scripts/startup.sh similarity index 100% rename from update/2024-12o/scripts/startup.sh rename to update/2024-1o/scripts/startup.sh diff --git a/update/2024-12o/stable/OpenScan.py b/update/2024-1o/stable/OpenScan.py similarity index 100% rename from update/2024-12o/stable/OpenScan.py rename to update/2024-1o/stable/OpenScan.py diff --git a/update/2024-12o/stable/OpenScanStatistics.py b/update/2024-1o/stable/OpenScanStatistics.py similarity index 100% rename from update/2024-12o/stable/OpenScanStatistics.py rename to update/2024-1o/stable/OpenScanStatistics.py diff --git a/update/2024-12o/stable/config.txt b/update/2024-1o/stable/config.txt similarity index 100% rename from update/2024-12o/stable/config.txt rename to update/2024-1o/stable/config.txt diff --git a/update/2024-12o/stable/fla.py b/update/2024-1o/stable/fla.py similarity index 100% rename from update/2024-12o/stable/fla.py rename to update/2024-1o/stable/fla.py diff --git a/update/2024-12o/stable/flows.json b/update/2024-1o/stable/flows.json similarity index 100% rename from update/2024-12o/stable/flows.json rename to update/2024-1o/stable/flows.json diff --git a/update/2024-12o/stable/settings.js b/update/2024-1o/stable/settings.js similarity index 100% rename from update/2024-12o/stable/settings.js rename to update/2024-1o/stable/settings.js diff --git a/update/2024-12o/update.json b/update/2024-1o/update.json similarity index 99% rename from update/2024-12o/update.json rename to update/2024-1o/update.json index 431b3c5..469545c 100644 --- a/update/2024-12o/update.json +++ b/update/2024-1o/update.json @@ -103,7 +103,7 @@ "6": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 343552 + "filesize": 343551 }, "7": { "src": "meanwhile/settings.js", From 3e969e16051282fe00f986c0be3fad14e47124cd Mon Sep 17 00:00:00 2001 From: Stealth Date: Wed, 2 Oct 2024 16:44:36 +0200 Subject: [PATCH 26/38] add a new way to get the configuration from the files, store it as a dict, store them as json and restore them to files again --- update/2024-1o/meanwhile/OpenScanConfig.py | 177 +++++++++++++++++++++ update/2024-1o/update.json | 5 + 2 files changed, 182 insertions(+) create mode 100755 update/2024-1o/meanwhile/OpenScanConfig.py diff --git a/update/2024-1o/meanwhile/OpenScanConfig.py b/update/2024-1o/meanwhile/OpenScanConfig.py new file mode 100755 index 0000000..a8eeaa9 --- /dev/null +++ b/update/2024-1o/meanwhile/OpenScanConfig.py @@ -0,0 +1,177 @@ +import json +import os +from datetime import datetime +from dataclasses import dataclass, asdict +from unicodedata import decimal + +@dataclass +class OpenScanConfig: + advanced_settings: bool + architecture: str + cam_awbg_blue: int + cam_awbg_red: int + cam_contrast: int + cam_cropx: int + cam_cropy: int + cam_delay_after: int + cam_delay_before: int + camera: str + cam_features: bool + cam_focus_max: float + cam_focus_min: float + cam_focuspeak: bool + cam_gain: int + cam_histogram: bool + cam_jpeg_quality: int + cam_mask: bool + cam_mask_threshold: int + cam_output_downscale: bool + cam_output_resolution: int + cam_preview_resolution: int + cam_rotation: int + cam_saturation: int + cam_sharparea: bool + cam_sharpness: int + cam_shutter: int + cam_stacksize: int + cam_timeout: int + datadog_api_token: str + datadog_enable: bool + delete_aborted: bool + diskspace_threshold: int + extra_acc: float + extra_accramp: int + extra_angle: int + extra_delay: int + extra_dir: int + extra_stepsperrotation: int + group_stack_photos: bool + hostname: str + interface_color: str + model: str + object_size: float + openscan_branch: str + openscan_uuid: str + openscan_version: str + osc_credit: int + osc_limit_filesize: int + osc_limit_photos: int + osc_splitsize: int + pin_external: int + pin_extra_dir: int + pin_extra_enable: int + pin_extra_endstop: int + pin_extra_step: int + pin_ringlight1: int + pin_ringlight2: int + pin_rotor_dir: int + pin_rotor_enable: int + pin_rotor_endstop: int + pin_rotor_step: int + pin_tt_dir: int + pin_tt_enable: int + pin_tt_step: int + raspberry_model: str + raspbian_codename: str + rotate_tt_first: bool + rotor_acc: int + rotor_accramp: int + rotor_angle: int + rotor_anglemax: int + rotor_anglemin:int + rotor_anglestart: int + rotor_delay: int + rotor_dir: int + rotor_enable_endstop: bool + rotor_endstop_angle: int + rotor_endstop_enable: bool + rotor_stepsperrotation: int + routine_photocount: int + routine_projectname: str + routine_secondpass: bool + sdcard_manfid: str + sdcard_name: str + session_token: str + shield_type: str + smb_enable: bool + ssh_enable: bool + status_cloud: str + status_internal_cam: str + telegram_api_token: str + telegram_client_id: str + telegram_enable: bool + terms: bool + token: str + tt_acc: float + tt_accramp: int + tt_angle: int + tt_delay: int + tt_dir: int + tt_stepsperrotation: int + turntable_mode: bool + updateable: bool + update_auto: bool + uploadprogress: str + @classmethod + def get_openscan_config(cls): + config = {} + blacklist = [ + 'token', + 'session_token', + 'telegram_api_token', + 'telegram_client_id', + 'status_uploadprogress', + 'raspberry_model', + 'sdcard_name', + 'sdcard_manfid' + ] # Add more keywords as needed + for field in cls.__dataclass_fields__: + if field not in blacklist: + try: + with open(f"/home/pi/OpenScan/settings/{field}", "r") as file: + value = file.read().strip() + field_type = cls.__annotations__[field] + if field_type == bool: + config[field] = value.lower() == 'true' + elif field_type == int: + config[field] = int(value) + elif field_type == float: + config[field] = float(value) + else: + config[field] = value + except FileNotFoundError: + print(f"Warning: File {field} not found. Skipping this field.") + except ValueError: + print(f"Warning: Could not convert value for {field}. Skipping this field.") + + return config + + @staticmethod + def export_config_to_file(config, file_path): + with open(file_path, "w") as json_file: + json.dump(config, json_file, indent=4) + +def get_openscan_config(): + return OpenScanConfig.get_openscan_config() + +def export_config_to_file(config, file_path): + OpenScanConfig.export_config_to_file(config, file_path) + +def persist_settings_from_file(config): + for field, value in config.items(): + if field in OpenScanConfig.__dataclass_fields__: + field_type = OpenScanConfig.__annotations__[field] + try: + if field_type == bool: + value = str(value).lower() + elif field_type in (int, float): + value = str(field_type(value)) + else: + value = str(value) + + with open(f"/home/pi/OpenScan/settings/{field}", "w") as file: + file.write(value) + except ValueError: + print(f"Warning: Could not convert value for {field}. Skipping this field.") + except IOError: + print(f"Warning: Could not write to file for {field}. Skipping this field.") diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index 469545c..3375ae8 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -81,6 +81,11 @@ "filesize": 10396 }, "2": { + "src": "meanwhile/OpenScanConfig.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanConfig.py", + "filesize": 5247 + }, + "3": { "src": "meanwhile/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", "filesize": 1288 From 88be18bb137984eaf72de9c2598a94379fd29968 Mon Sep 17 00:00:00 2001 From: Stealth Date: Wed, 2 Oct 2024 17:56:44 +0200 Subject: [PATCH 27/38] add settings logic --- ...{OpenScanConfig.py => OpenScanSettings.py} | 36 ++--- update/2024-1o/meanwhile/config.txt | 2 +- update/2024-1o/meanwhile/fla.py | 24 ++- update/2024-1o/meanwhile/flows.json | 151 +++++++++++++----- update/2024-1o/meanwhile/settings.js | 3 + update/2024-1o/update.json | 24 +-- 6 files changed, 164 insertions(+), 76 deletions(-) rename update/2024-1o/meanwhile/{OpenScanConfig.py => OpenScanSettings.py} (83%) diff --git a/update/2024-1o/meanwhile/OpenScanConfig.py b/update/2024-1o/meanwhile/OpenScanSettings.py similarity index 83% rename from update/2024-1o/meanwhile/OpenScanConfig.py rename to update/2024-1o/meanwhile/OpenScanSettings.py index a8eeaa9..2e77749 100755 --- a/update/2024-1o/meanwhile/OpenScanConfig.py +++ b/update/2024-1o/meanwhile/OpenScanSettings.py @@ -5,7 +5,7 @@ from unicodedata import decimal @dataclass -class OpenScanConfig: +class OpenScanSettings: advanced_settings: bool architecture: str cam_awbg_blue: int @@ -113,8 +113,8 @@ class OpenScanConfig: update_auto: bool uploadprogress: str @classmethod - def get_openscan_config(cls): - config = {} + def get_openscan_settings(cls): + settings = {} blacklist = [ 'token', 'session_token', @@ -132,35 +132,35 @@ def get_openscan_config(cls): value = file.read().strip() field_type = cls.__annotations__[field] if field_type == bool: - config[field] = value.lower() == 'true' + settings[field] = value.lower() == 'true' elif field_type == int: - config[field] = int(value) + settings[field] = int(value) elif field_type == float: - config[field] = float(value) + settings[field] = float(value) else: - config[field] = value + settings[field] = value except FileNotFoundError: print(f"Warning: File {field} not found. Skipping this field.") except ValueError: print(f"Warning: Could not convert value for {field}. Skipping this field.") - return config + return settings @staticmethod - def export_config_to_file(config, file_path): + def export_settings_to_file(settings, file_path): with open(file_path, "w") as json_file: - json.dump(config, json_file, indent=4) + json.dump(settings, json_file, indent=4) -def get_openscan_config(): - return OpenScanConfig.get_openscan_config() +def get_openscan_settings(): + return OpenScanSettings.get_openscan_settings() -def export_config_to_file(config, file_path): - OpenScanConfig.export_config_to_file(config, file_path) +def export_settings_to_file(settings, file_path): + OpenScanSettings.export_settings_to_file(settings, file_path) -def persist_settings_from_file(config): - for field, value in config.items(): - if field in OpenScanConfig.__dataclass_fields__: - field_type = OpenScanConfig.__annotations__[field] +def persist_settings_from_file(settings): + for field, value in settings.items(): + if field in OpenScanSettings.__dataclass_fields__: + field_type = OpenScanSettings.__annotations__[field] try: if field_type == bool: value = str(value).lower() diff --git a/update/2024-1o/meanwhile/config.txt b/update/2024-1o/meanwhile/config.txt index 3bb8fef..f601472 100755 --- a/update/2024-1o/meanwhile/config.txt +++ b/update/2024-1o/meanwhile/config.txt @@ -5,7 +5,7 @@ # Additional overlays and parameters are documented /boot/overlays/README # Automatically load overlays for detected cameras -camera_auto_detect=1 +# camera_auto_detect=1 # Automatically load overlays for detected DSI displays display_auto_detect=1 diff --git a/update/2024-1o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py index 026867e..2e74785 100644 --- a/update/2024-1o/meanwhile/fla.py +++ b/update/2024-1o/meanwhile/fla.py @@ -1,14 +1,16 @@ +from zipfile import ZipFile from flask import Flask, request, redirect, send_file, send_from_directory from flask_restx import Resource, Api, Namespace from picamera2 import Picamera2 from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time from OpenScan import load_int, load_float, load_bool, ringlight, motorrun +from OpenScanSettings import get_openscan_settings, export_settings_to_file import RPi.GPIO as GPIO from math import sqrt import os import math -from skimage import feature, color, transform +#from skimage import feature, color, transform import numpy as np from scipy import ndimage import socket @@ -115,6 +117,24 @@ def get(self): else: return {"status": "idle"}, 200 +@system_ns.route('/get_settings') +class SendSettingsFile(Resource): + def get(self): + openscan_tmp_folder = '/home/pi/OpenScan/tmp2' + file_name = 'settings.zip' + openscan_settings = get_openscan_settings() + export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name) + + with ZipFile(file_name, 'w') as zip_object: + zip_object.write(openscan_tmp_folder + "/" + file_name) + + if os.path.exists(openscan_tmp_folder + "/" + file_name): + print("ZIP file created") + else: + print("ZIP file not created") + return send_from_directory(openscan_tmp_folder, file_name, as_attachment=True) + + @system_ns.route('/shutdown') class Shutdown(Resource): @system_ns.doc(params={'token': 'Shutdown token for authentication'}) @@ -512,8 +532,6 @@ def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') - if __name__ == '__main__': -# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/2024-1o/meanwhile/flows.json b/update/2024-1o/meanwhile/flows.json index f248be5..265e53a 100644 --- a/update/2024-1o/meanwhile/flows.json +++ b/update/2024-1o/meanwhile/flows.json @@ -522,6 +522,40 @@ "collapse": false, "className": "" }, + { + "id": "dfb4a60f.d788f8", + "type": "ui_group", + "name": "Data Export", + "tab": "48418b79.0f5834", + "order": 1, + "disp": true, + "width": "12" + }, + { + "id": "48418b79.0f5834", + "type": "ui_tab", + "name": "Dashboard", + "icon": "dashboard", + "order": 1 + }, + { + "id": "c33a1024a72aa169", + "type": "ui_group", + "name": "Default", + "tab": "cc6c4310cf7b61cc", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "cc6c4310cf7b61cc", + "type": "ui_tab", + "name": "Home", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -871,13 +905,13 @@ "order": 14, "width": 7, "height": 1, - "format": "\n\n", + "format": "
Some text
\n\n\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "global", "className": "", - "x": 650, + "x": 470, "y": 360, "wires": [ [] @@ -1249,7 +1283,7 @@ "type": "function", "z": "e6f4d02efb300ea9", "name": "loadl", - "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = String(data);\nflow.set('openscan_version',data)\nreturn msg", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.openscan_version = String(data);\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -1259,7 +1293,8 @@ "y": 360, "wires": [ [ - "b1b0ccb783dd5882" + "0d8c6bc7887fb3c2", + "fa6db57803ae2b6d" ] ] }, @@ -1276,38 +1311,10 @@ "targetType": "full", "statusVal": "", "statusType": "auto", - "x": 590, - "y": 460, + "x": 430, + "y": 400, "wires": [] }, - { - "id": "b1b0ccb783dd5882", - "type": "change", - "z": "e6f4d02efb300ea9", - "name": "openscan_version", - "rules": [ - { - "t": "set", - "p": "openscan_version", - "pt": "flow", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 415, - "y": 360, - "wires": [ - [ - "fa6db57803ae2b6d", - "0d8c6bc7887fb3c2" - ] - ] - }, { "id": "6a3d9acbe097a3d2", "type": "function", @@ -2674,8 +2681,8 @@ "z": "481edaf6db5a7a54", "name": "start routine settings", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "50eeb3e362f9027f", + "960912e90ba5b5bc" ], "x": 95, "y": 480, @@ -9033,7 +9040,7 @@ "type": "ui_text", "z": "a5557543ccff5889", "group": "ddbd496e.93a288", - "order": 3, + "order": 2, "width": 0, "height": 0, "name": "", @@ -9107,7 +9114,7 @@ "tooltip": "", "place": "Select option", "group": "ddbd496e.93a288", - "order": 5, + "order": 4, "width": 2, "height": 1, "passthru": false, @@ -9163,7 +9170,7 @@ "type": "ui_text", "z": "a5557543ccff5889", "group": "ddbd496e.93a288", - "order": 4, + "order": 3, "width": 4, "height": 1, "name": "", @@ -9183,7 +9190,7 @@ "label": "Auto-check update availability", "tooltip": "", "group": "ddbd496e.93a288", - "order": 6, + "order": 5, "width": 6, "height": 1, "passthru": true, @@ -9287,7 +9294,7 @@ "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", - "order": 7, + "order": 6, "width": 6, "height": 1, "passthru": false, @@ -9648,7 +9655,7 @@ "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", - "order": 8, + "order": 7, "width": 6, "height": 1, "passthru": false, @@ -9706,6 +9713,66 @@ [] ] }, + { + "id": "28cab3989dcf898b", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "name": "Download Config", + "order": 9, + "width": 0, + "height": 0, + "format": "\n\n
Download Config\n
\n
\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 530, + "y": 1020, + "wires": [ + [] + ] + }, + { + "id": "c5268070.c55a3", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "name": "Upload Btn", + "order": 8, + "width": "3", + "height": 1, + "format": "\n\n \n\n\n\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 510, + "y": 1060, + "wires": [ + [ + "5e18b80e617a3db8" + ] + ] + }, + { + "id": "5e18b80e617a3db8", + "type": "debug", + "z": "a5557543ccff5889", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 750, + "y": 1060, + "wires": [] + }, { "id": "9b3e6a06c82a0f52", "type": "link in", diff --git a/update/2024-1o/meanwhile/settings.js b/update/2024-1o/meanwhile/settings.js index 357b02b..b71b7f3 100644 --- a/update/2024-1o/meanwhile/settings.js +++ b/update/2024-1o/meanwhile/settings.js @@ -232,6 +232,9 @@ userDir: '/home/pi/OpenScan/settings/.node-red/', // {path: '/home/nol/pics/', root: "/img/"}, // {path: '/home/nol/reports/', root: "/doc/"}, //], + httpStatic: [ + {path: '/home/pi/OpenScan/tmp2/', root: "/tmp2/"} + ], /** * All static routes will be appended to httpStaticRoot diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index 3375ae8..acb5984 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -81,39 +81,39 @@ "filesize": 10396 }, "2": { - "src": "meanwhile/OpenScanConfig.py", - "dst": "/usr/lib/python3/dist-packages/OpenScanConfig.py", - "filesize": 5247 + "src": "meanwhile/OpenScanSettings.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanSettings.py", + "filesize": 5293 }, "3": { "src": "meanwhile/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", "filesize": 1288 }, - "3": { + "4": { "src": "meanwhile/config.txt", "dst": "/boot/config.txt", - "filesize": 864 + "filesize": 866 }, - "4": { + "5": { "src": "meanwhile/expand_root.sh", "dst": "/home/pi/OpenScan/files/expand_root.sh", "filesize": 170 }, - "5": { + "6": { "src": "meanwhile/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 17869 + "filesize": 18593 }, - "6": { + "7": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 343551 + "filesize": 346256 }, - "7": { + "8": { "src": "meanwhile/settings.js", "dst": "/root/.node-red/settings.js", - "filesize": 21248 + "filesize": 21332 } } } \ No newline at end of file From 79c4ba3b527b87c1668b0b36a3ee2777bdc0e84a Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 5 Oct 2024 16:03:03 +0200 Subject: [PATCH 28/38] get_settings --- update/2024-1o/meanwhile/fla.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/update/2024-1o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py index 2e74785..2a94655 100644 --- a/update/2024-1o/meanwhile/fla.py +++ b/update/2024-1o/meanwhile/fla.py @@ -121,18 +121,17 @@ def get(self): class SendSettingsFile(Resource): def get(self): openscan_tmp_folder = '/home/pi/OpenScan/tmp2' - file_name = 'settings.zip' + file_name = 'settings' openscan_settings = get_openscan_settings() - export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name) + export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name + '.json') - with ZipFile(file_name, 'w') as zip_object: - zip_object.write(openscan_tmp_folder + "/" + file_name) - - if os.path.exists(openscan_tmp_folder + "/" + file_name): + with ZipFile(openscan_tmp_folder + "/" + file_name + '.zip', 'w') as zip_object: + zip_object.write(openscan_tmp_folder + "/" + file_name + "json", zip_object.ZIP_DEFLATED) + if os.path.exists(openscan_tmp_folder + "/" + file_name + ".zip"): print("ZIP file created") else: print("ZIP file not created") - return send_from_directory(openscan_tmp_folder, file_name, as_attachment=True) + return send_from_directory(openscan_tmp_folder, file_name + ".zip", as_attachment=True) @system_ns.route('/shutdown') From 8b76ccd7333c31d895bb2ce678e95b4cc7b0ad6f Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 5 Oct 2024 20:45:21 +0200 Subject: [PATCH 29/38] update --- update/2024-1o/meanwhile/flows.json | 37 ++++++++++++++++++++++------- update/2024-1o/update.json | 4 ++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/update/2024-1o/meanwhile/flows.json b/update/2024-1o/meanwhile/flows.json index 265e53a..e945125 100644 --- a/update/2024-1o/meanwhile/flows.json +++ b/update/2024-1o/meanwhile/flows.json @@ -63,7 +63,7 @@ "type": "ui_tab", "name": "Scan", "icon": "dashboard", - "order": 2, + "order": 3, "disabled": false, "hidden": false }, @@ -173,7 +173,7 @@ "link": "https://openscan-org.github.io/OpenScan-Doc/", "icon": "fa-bookmark", "target": "iframe", - "order": 8 + "order": 9 }, { "id": "23f75a8768250ce8", @@ -182,7 +182,7 @@ "link": "https://www.patreon.com/OpenScan", "icon": "fa-bookmark", "target": "newtab", - "order": 7 + "order": 8 }, { "id": "b5fdd57b.15eda8", @@ -209,7 +209,7 @@ "type": "ui_tab", "name": "Files&Cloud", "icon": "dashboard", - "order": 3, + "order": 4, "disabled": false, "hidden": false }, @@ -281,7 +281,7 @@ "type": "ui_tab", "name": "Update & Info", "icon": "dashboard", - "order": 4, + "order": 6, "disabled": false, "hidden": false }, @@ -487,7 +487,7 @@ "type": "ui_tab", "name": "Statistics", "icon": "dashboard", - "order": 6, + "order": 7, "disabled": false, "hidden": false }, @@ -536,7 +536,7 @@ "type": "ui_tab", "name": "Dashboard", "icon": "dashboard", - "order": 1 + "order": 2 }, { "id": "c33a1024a72aa169", @@ -553,6 +553,7 @@ "type": "ui_tab", "name": "Home", "icon": "dashboard", + "order": 10, "disabled": false, "hidden": false }, @@ -905,7 +906,7 @@ "order": 14, "width": 7, "height": 1, - "format": "
Some text
\n\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, @@ -9031,7 +9032,8 @@ "1bbe2d769f42c313" ], [ - "fefe45404bdb19c4" + "fefe45404bdb19c4", + "44573d9223b2a75d" ] ] }, @@ -9773,6 +9775,23 @@ "y": 1060, "wires": [] }, + { + "id": "44573d9223b2a75d", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 8", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 720, + "y": 380, + "wires": [] + }, { "id": "9b3e6a06c82a0f52", "type": "link in", diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index acb5984..d1dd847 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -103,12 +103,12 @@ "6": { "src": "meanwhile/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 18593 + "filesize": 18687 }, "7": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 346256 + "filesize": 346421 }, "8": { "src": "meanwhile/settings.js", From a57436c36d1b177731440a7d54e346385ab79146 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 5 Oct 2024 21:24:38 +0200 Subject: [PATCH 30/38] update meanwhile --- update/2024-1o/beta/config.txt | 2 +- update/2024-1o/beta/fla.py | 23 +- update/2024-1o/beta/flows.json | 249 ++++++++++++++----- update/2024-1o/beta/settings.js | 3 + update/2024-1o/meanwhile/OpenScanSettings.py | 15 +- update/2024-1o/meanwhile/flows.json | 42 +++- update/2024-1o/update.json | 29 ++- 7 files changed, 275 insertions(+), 88 deletions(-) diff --git a/update/2024-1o/beta/config.txt b/update/2024-1o/beta/config.txt index 3bb8fef..f601472 100755 --- a/update/2024-1o/beta/config.txt +++ b/update/2024-1o/beta/config.txt @@ -5,7 +5,7 @@ # Additional overlays and parameters are documented /boot/overlays/README # Automatically load overlays for detected cameras -camera_auto_detect=1 +# camera_auto_detect=1 # Automatically load overlays for detected DSI displays display_auto_detect=1 diff --git a/update/2024-1o/beta/fla.py b/update/2024-1o/beta/fla.py index 026867e..2a94655 100644 --- a/update/2024-1o/beta/fla.py +++ b/update/2024-1o/beta/fla.py @@ -1,14 +1,16 @@ +from zipfile import ZipFile from flask import Flask, request, redirect, send_file, send_from_directory from flask_restx import Resource, Api, Namespace from picamera2 import Picamera2 from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time from OpenScan import load_int, load_float, load_bool, ringlight, motorrun +from OpenScanSettings import get_openscan_settings, export_settings_to_file import RPi.GPIO as GPIO from math import sqrt import os import math -from skimage import feature, color, transform +#from skimage import feature, color, transform import numpy as np from scipy import ndimage import socket @@ -115,6 +117,23 @@ def get(self): else: return {"status": "idle"}, 200 +@system_ns.route('/get_settings') +class SendSettingsFile(Resource): + def get(self): + openscan_tmp_folder = '/home/pi/OpenScan/tmp2' + file_name = 'settings' + openscan_settings = get_openscan_settings() + export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name + '.json') + + with ZipFile(openscan_tmp_folder + "/" + file_name + '.zip', 'w') as zip_object: + zip_object.write(openscan_tmp_folder + "/" + file_name + "json", zip_object.ZIP_DEFLATED) + if os.path.exists(openscan_tmp_folder + "/" + file_name + ".zip"): + print("ZIP file created") + else: + print("ZIP file not created") + return send_from_directory(openscan_tmp_folder, file_name + ".zip", as_attachment=True) + + @system_ns.route('/shutdown') class Shutdown(Resource): @system_ns.doc(params={'token': 'Shutdown token for authentication'}) @@ -512,8 +531,6 @@ def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') - if __name__ == '__main__': -# app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) diff --git a/update/2024-1o/beta/flows.json b/update/2024-1o/beta/flows.json index ee66e6c..7827fd0 100644 --- a/update/2024-1o/beta/flows.json +++ b/update/2024-1o/beta/flows.json @@ -63,7 +63,7 @@ "type": "ui_tab", "name": "Scan", "icon": "dashboard", - "order": 2, + "order": 3, "disabled": false, "hidden": false }, @@ -173,7 +173,7 @@ "link": "https://openscan-org.github.io/OpenScan-Doc/", "icon": "fa-bookmark", "target": "iframe", - "order": 8 + "order": 9 }, { "id": "23f75a8768250ce8", @@ -182,7 +182,7 @@ "link": "https://www.patreon.com/OpenScan", "icon": "fa-bookmark", "target": "newtab", - "order": 7 + "order": 8 }, { "id": "b5fdd57b.15eda8", @@ -209,7 +209,7 @@ "type": "ui_tab", "name": "Files&Cloud", "icon": "dashboard", - "order": 3, + "order": 4, "disabled": false, "hidden": false }, @@ -281,7 +281,7 @@ "type": "ui_tab", "name": "Update & Info", "icon": "dashboard", - "order": 4, + "order": 6, "disabled": false, "hidden": false }, @@ -487,7 +487,7 @@ "type": "ui_tab", "name": "Statistics", "icon": "dashboard", - "order": 6, + "order": 7, "disabled": false, "hidden": false }, @@ -522,6 +522,41 @@ "collapse": false, "className": "" }, + { + "id": "dfb4a60f.d788f8", + "type": "ui_group", + "name": "Data Export", + "tab": "48418b79.0f5834", + "order": 1, + "disp": true, + "width": "12" + }, + { + "id": "48418b79.0f5834", + "type": "ui_tab", + "name": "Dashboard", + "icon": "dashboard", + "order": 2 + }, + { + "id": "c33a1024a72aa169", + "type": "ui_group", + "name": "Default", + "tab": "cc6c4310cf7b61cc", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "cc6c4310cf7b61cc", + "type": "ui_tab", + "name": "Home", + "icon": "dashboard", + "order": 10, + "disabled": false, + "hidden": false + }, { "id": "bc4e2c03859196c3", "type": "inject", @@ -609,7 +644,7 @@ "type": "function", "z": "e6f4d02efb300ea9", "name": "CREATE FACTORY DEFAULT", - "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'openscan_branch':'stable',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", + "func": "msg = { \n'overwrite':msg.overwrite,\n'settings':\n {\n 'advanced_settings':false,\n 'cam_awbg_blue':0,\n 'cam_awbg_red':0,\n 'cam_contrast':1,\n 'cam_cropx':0,\n 'cam_cropy':0,\n 'cam_delay_after':0,\n 'cam_delay_before':0,\n 'camera':'',\n 'cam_features' : false,\n 'cam_focus_min': 11.5,\n 'cam_focus_max': 11.5,\n 'cam_gain':1,\n 'cam_jpeg_quality':95,\n 'cam_rotation':270,\n 'cam_saturation':1,\n 'cam_stacksize':1,\n 'cam_shutter':50000,\n 'cam_timeout':800,\n 'cam_mask_threshold':45,\n 'cam_mask':true,\n 'hostname':'openscan',\n 'model':'',\n 'osc_credit':'',\n 'osc_limit_filesize':'',\n 'osc_limit_photos':'',\n 'osc_splitsize':200000000,\n// 'pin_extra_endstop': 19,\n 'pin_external': 25,\n 'pin_ringlight1': 24,\n 'pin_ringlight2': 24,\n \n 'pin_rotor_endstop': 17,\n 'pin_rotor_dir': 23,\n 'pin_rotor_enable': 22,\n 'pin_rotor_step': 27,\n 'rotor_acc': 0.5,\n 'rotor_accramp': 500,\n 'rotor_angle': 10,\n 'rotor_anglemax': 75,\n 'rotor_anglemin': -25,\n 'rotor_anglestart': 25,\n 'rotor_delay': 0.0002,\n 'rotor_dir': 1,\n 'rotor_stepsperrotation': 35200/2,\n 'rotor_endstop_angle': 0,\n 'rotor_endstop_enable': false,\n\n // 'pin_tt_endstop': 25,\n 'pin_tt_dir': 6,\n 'pin_tt_enable': 22,\n 'pin_tt_step': 16,\n 'tt_acc': 1,\n 'tt_accramp': 200,\n 'tt_angle': 90,\n 'tt_delay': 0.0001,\n 'tt_dir': 1,\n 'tt_stepsperrotation': 1600,\n\n 'pin_extra_dir': 21,\n 'pin_extra_step': 20,\n 'pin_extra_enable': 22,\n 'extra_acc': 1,\n 'extra_accramp': 200,\n 'extra_angle': 10,\n 'extra_delay': 0.0001,\n 'extra_dir': 1,\n 'extra_stepsperrotation': 3200,\n\n 'routine_photocount':50,\n 'routine_projectname':'default',\n 'smb':true,\n 'ssh':true,\n 'status_cloud':'ready',\n 'status_internal_cam':'--READY--',\n 'status_uploadprogress':'',\n 'terms':false,\n 'token':'',\n\n 'uploadprogress':'',\n 'openscan_branch':'beta',\n 'update_auto':true,\n 'turntable_mode':false,\n 'diskspace_threshold':4000,\n 'updateable':false,\n 'cam_focuspeak':false,\n 'cam_histogram':false,\n 'routine_secondpass':true,\n 'cam_output_resolution':20000000,\n 'cam_preview_resolution':2000000,\n 'cam_output_downscale':false,\n 'cam_sharparea':false,\n 'cam_sharpness':100,\n}}\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -871,13 +906,13 @@ "order": 14, "width": 7, "height": 1, - "format": "\n\n", + "format": "\n\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "global", "className": "", - "x": 650, + "x": 470, "y": 360, "wires": [ [] @@ -1249,7 +1284,7 @@ "type": "function", "z": "e6f4d02efb300ea9", "name": "loadl", - "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.payload = String(data);\nflow.set('openscan_version',data)\nreturn msg", + "func": "let fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar file = 'openscan_version'\nconst data = fs.readFileSync(filepath + file, 'utf8');\nmsg.openscan_version = String(data);\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -1259,36 +1294,27 @@ "y": 360, "wires": [ [ - "b1b0ccb783dd5882" + "0d8c6bc7887fb3c2", + "fa6db57803ae2b6d" ] ] }, { - "id": "b1b0ccb783dd5882", - "type": "change", + "id": "fa6db57803ae2b6d", + "type": "debug", "z": "e6f4d02efb300ea9", - "name": "openscan_version", - "rules": [ - { - "t": "set", - "p": "openscan_version", - "pt": "flow", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 415, - "y": 360, - "wires": [ - [ - "0d8c6bc7887fb3c2" - ] - ] + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": true, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 430, + "y": 400, + "wires": [] }, { "id": "6a3d9acbe097a3d2", @@ -2656,8 +2682,8 @@ "z": "481edaf6db5a7a54", "name": "start routine settings", "links": [ - "960912e90ba5b5bc", - "50eeb3e362f9027f" + "50eeb3e362f9027f", + "960912e90ba5b5bc" ], "x": 95, "y": 480, @@ -8885,7 +8911,9 @@ "x": 260, "y": 140, "wires": [ - [] + [ + "e23c514008cad1a1" + ] ] }, { @@ -8973,12 +9001,29 @@ "y": 260, "wires": [] }, + { + "id": "e23c514008cad1a1", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 140, + "wires": [] + }, { "id": "b0629875a30ae1d7", "type": "python3-function", "z": "a5557543ccff5889", "name": "get update", - "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\nopenscan_branch = load_str('openscan_branch')\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\n\nupdatepath = '/home/pi/OpenScan/updates/'\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + '/update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\nmsg['openscan_version'] = openscan_version\nmsg['openscan_branch'] = openscan_branch\nmsg['url'] = url\nreturn msg, msg", + "func": "import json\nimport requests\nfrom OpenScan import load_str\n\nif not msg['payload']:\n msg['status'] = '--READY--'\n return msg\n\nopenscan_version = load_str('openscan_version')\nopenscan_branch = load_str('openscan_branch')\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = 'https://raw.githubusercontent.com/stealthizer/OpenScan2/' + openscan_version + '/update/' + openscan_version\nupdatepath_temp = updatepath + 'update_temp.json'\nupdatepath_old = updatepath + 'update.json'\n\nr = requests.get(url + '/update.json')\n\nif r.status_code != 200:\n msg['status'] = 'no internet connection'\n return msg\n\nwith open(updatepath_temp, 'wb+') as file:\n file.write(r.content)\nwith open(updatepath_temp, 'r') as file:\n msg = json.load(file)\n\nmsg['status'] = 'checking updates'\nmsg['url'] = url\nmsg['openscan_branch'] = openscan_branch\nmsg['openscan_version'] = openscan_version\n\nreturn msg, msg", "outputs": 2, "x": 390, "y": 540, @@ -8987,7 +9032,8 @@ "1bbe2d769f42c313" ], [ - "fefe45404bdb19c4" + "fefe45404bdb19c4", + "44573d9223b2a75d" ] ] }, @@ -8996,7 +9042,7 @@ "type": "ui_text", "z": "a5557543ccff5889", "group": "ddbd496e.93a288", - "order": 3, + "order": 2, "width": 0, "height": 0, "name": "", @@ -9013,7 +9059,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "check files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nopenscan_branch = msg['openscan_branch']\nopenscan_version = msg['openscan_version']\nurl = msg['url']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[openscan_branch]:\n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[openscan_branch][i]['filesize2'] = filesize\n if filesize == msg[openscan_branch][i]['filesize']:\n msg[openscan_branch][i]['update'] = False\n continue\n msg[openscan_branch][i]['update'] = True\n\n counter += 1\n\nmsg['openscan_branch'] = openscan_branch\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str\n\nopenscan_version = msg['openscan_version']\nopenscan_branch = msg['openscan_branch']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = msg['url']\nupdatepath_old = updatepath + 'update.json'\n\ncounter = 0\nfor i in msg[openscan_branch]:\n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n if os.path.isfile(temp):\n filesize = os.path.getsize(temp)\n msg[openscan_branch][i]['filesize2'] = filesize\n if filesize == msg[openscan_branch][i]['filesize']:\n msg[openscan_branch][i]['update'] = False\n continue\n msg[openscan_branch][i]['update'] = True\n\n counter += 1\n\nif counter == 0:\n msg['status'] = 'No new update available'\nelse:\n msg['status'] = 'New update available'\n msg['topic'] = msg['status']\n msg['payload'] = 'Install & reboot now?'\n\nmsg['counter'] = counter\n\nreturn msg\n", "outputs": 1, "x": 550, "y": 560, @@ -9053,8 +9099,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 370, - "y": 420, + "x": 210, + "y": 440, "wires": [ [ "2852023f3aa8db10" @@ -9070,7 +9116,7 @@ "tooltip": "", "place": "Select option", "group": "ddbd496e.93a288", - "order": 5, + "order": 4, "width": 2, "height": 1, "passthru": false, @@ -9096,8 +9142,8 @@ "topic": "topic", "topicType": "msg", "className": "", - "x": 500, - "y": 420, + "x": 390, + "y": 440, "wires": [ [ "1e10b387ee30c486" @@ -9115,8 +9161,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 630, - "y": 420, + "x": 520, + "y": 440, "wires": [ [] ] @@ -9126,7 +9172,7 @@ "type": "ui_text", "z": "a5557543ccff5889", "group": "ddbd496e.93a288", - "order": 4, + "order": 3, "width": 4, "height": 1, "name": "", @@ -9134,8 +9180,8 @@ "format": "{{msg.payload}}", "layout": "row-spread", "className": "", - "x": 760, - "y": 420, + "x": 650, + "y": 440, "wires": [] }, { @@ -9146,7 +9192,7 @@ "label": "Auto-check update availability", "tooltip": "", "group": "ddbd496e.93a288", - "order": 6, + "order": 5, "width": 6, "height": 1, "passthru": true, @@ -9164,8 +9210,8 @@ "offcolor": "", "animate": false, "className": "", - "x": 450, - "y": 480, + "x": 449, + "y": 491, "wires": [ [ "1ab4c6b4b232a022" @@ -9183,8 +9229,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 650, - "y": 480, + "x": 649, + "y": 491, "wires": [ [] ] @@ -9250,7 +9296,7 @@ "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", - "order": 7, + "order": 6, "width": 6, "height": 1, "passthru": false, @@ -9277,7 +9323,7 @@ "type": "python3-function", "z": "a5557543ccff5889", "name": "download files", - "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = load_str('openscan_version')\n\nopenscan_branch = msg['openscan_branch']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = msg['url']\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[openscan_branch]:\n if msg[openscan_branch][i]['update'] == False:\n continue\n \n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + '/' + msg[openscan_branch][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[openscan_branch][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[openscan_branch][i]['dst'])\n \n if msg[openscan_branch][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", + "func": "import json\nimport requests\nimport shutil\nfrom OpenScan import load_str, save\n\nopenscan_version = msg['openscan_version']\nopenscan_branch = msg['openscan_branch']\n\nupdatepath = '/home/pi/OpenScan/updates/'\nurl = msg['url']\n\nif msg['payload'] != 'YES':\n return\n\ncounter = 0\n\nfor i in msg[openscan_branch]:\n if msg[openscan_branch][i]['update'] == False:\n continue\n \n filepath = msg[openscan_branch][i]['dst']\n temp = updatepath + os.path.basename(filepath)\n \n r = requests.get(url + '/' + msg[openscan_branch][i]['src'])\n if r.status_code != 200:\n msg['status'] = 'downloading ' + msg[openscan_branch][i]['src'] + ' failed'\n return msg\n with open(temp, 'wb+') as file:\n file.write(r.content)\n shutil.copy(temp, msg[openscan_branch][i]['dst'])\n \n if msg[openscan_branch][i]['dst'] == '/boot/config.txt':\n save('camera','')\n \n counter += 1\n\nmsg['status'] = 'Installed ' + str(counter) + ' of ' + str(msg['counter']) + ' - restarting ...'\n\nif counter == msg['counter']:\n updatepath_temp = updatepath + 'update_temp.json'\n updatepath_old = updatepath + 'update.json'\n shutil.move(updatepath_temp, updatepath_old)\n\nreturn msg\n", "outputs": 1, "x": 880, "y": 560, @@ -9325,14 +9371,14 @@ "type": "function", "z": "a5557543ccff5889", "name": "loadB", - "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", + "func": "var file = 'update_auto'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\ndata = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\n\nmsg.payload = data\n\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 217, - "y": 508, + "x": 210, + "y": 480, "wires": [ [ "b0629875a30ae1d7", @@ -9611,7 +9657,7 @@ "z": "a5557543ccff5889", "name": "", "group": "ddbd496e.93a288", - "order": 8, + "order": 7, "width": 6, "height": 1, "passthru": false, @@ -9669,6 +9715,83 @@ [] ] }, + { + "id": "28cab3989dcf898b", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "name": "Download Config", + "order": 9, + "width": 0, + "height": 0, + "format": "\n\n
Download Config\n
\n
\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 530, + "y": 1020, + "wires": [ + [] + ] + }, + { + "id": "c5268070.c55a3", + "type": "ui_template", + "z": "a5557543ccff5889", + "group": "ddbd496e.93a288", + "name": "Upload Btn", + "order": 8, + "width": "3", + "height": 1, + "format": "\n\n \n\n\n\n", + "storeOutMessages": true, + "fwdInMessages": true, + "resendOnRefresh": false, + "templateScope": "local", + "className": "", + "x": 510, + "y": 1060, + "wires": [ + [ + "5e18b80e617a3db8" + ] + ] + }, + { + "id": "5e18b80e617a3db8", + "type": "debug", + "z": "a5557543ccff5889", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 750, + "y": 1060, + "wires": [] + }, + { + "id": "44573d9223b2a75d", + "type": "debug", + "z": "a5557543ccff5889", + "name": "debug 8", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 720, + "y": 380, + "wires": [] + }, { "id": "9b3e6a06c82a0f52", "type": "link in", diff --git a/update/2024-1o/beta/settings.js b/update/2024-1o/beta/settings.js index 357b02b..b71b7f3 100644 --- a/update/2024-1o/beta/settings.js +++ b/update/2024-1o/beta/settings.js @@ -232,6 +232,9 @@ userDir: '/home/pi/OpenScan/settings/.node-red/', // {path: '/home/nol/pics/', root: "/img/"}, // {path: '/home/nol/reports/', root: "/doc/"}, //], + httpStatic: [ + {path: '/home/pi/OpenScan/tmp2/', root: "/tmp2/"} + ], /** * All static routes will be appended to httpStaticRoot diff --git a/update/2024-1o/meanwhile/OpenScanSettings.py b/update/2024-1o/meanwhile/OpenScanSettings.py index 2e77749..7930873 100755 --- a/update/2024-1o/meanwhile/OpenScanSettings.py +++ b/update/2024-1o/meanwhile/OpenScanSettings.py @@ -112,9 +112,12 @@ class OpenScanSettings: updateable: bool update_auto: bool uploadprogress: str + @classmethod def get_openscan_settings(cls): - settings = {} + settings = { + "version": "1.0" # Add a version identifier + } blacklist = [ 'token', 'session_token', @@ -147,14 +150,20 @@ def get_openscan_settings(cls): return settings @staticmethod - def export_settings_to_file(settings, file_path): + def export_settings_to_file(settings, file_path=None): + from datetime import datetime + + if file_path is None: + current_date = datetime.now().strftime("%Y-%m-%d") + file_path = f"openscan-settings-{current_date}.json" + with open(file_path, "w") as json_file: json.dump(settings, json_file, indent=4) def get_openscan_settings(): return OpenScanSettings.get_openscan_settings() -def export_settings_to_file(settings, file_path): +def export_settings_to_file(settings, file_path=None): OpenScanSettings.export_settings_to_file(settings, file_path) def persist_settings_from_file(settings): diff --git a/update/2024-1o/meanwhile/flows.json b/update/2024-1o/meanwhile/flows.json index e945125..3a4336e 100644 --- a/update/2024-1o/meanwhile/flows.json +++ b/update/2024-1o/meanwhile/flows.json @@ -4120,7 +4120,7 @@ "name": "Network", "info": "", "x": 100, - "y": 720, + "y": 760, "wires": [] }, { @@ -7530,7 +7530,7 @@ "topic": "Restore default settings?", "topicType": "str", "x": 110, - "y": 620, + "y": 680, "wires": [ [ "53e6681d7254d484" @@ -7553,7 +7553,7 @@ "topic": "", "name": "", "x": 270, - "y": 620, + "y": 680, "wires": [ [ "c11e79cfa7bc10b7" @@ -7572,7 +7572,7 @@ "finalize": "", "libs": [], "x": 410, - "y": 620, + "y": 680, "wires": [ [ "307782d10c1acdaf" @@ -7589,7 +7589,7 @@ "38783aea9cc317a6" ], "x": 505, - "y": 620, + "y": 680, "wires": [] }, { @@ -8901,6 +8901,35 @@ "y": 1760, "wires": [] }, + { + "id": "d9d7acf381c002cf", + "type": "python3-function", + "z": "e43a27722b508115", + "name": "restart interface", + "func": "import os\nfrom time import sleep\nsleep(1.5)\nimport RPi.GPIO as GPIO\nGPIO.setwarnings(False)\nGPIO.cleanup()\nos.system('service flask restart')\nos.system('service nodered restart')\n", + "outputs": 1, + "x": 300, + "y": 600, + "wires": [ + [] + ] + }, + { + "id": "acfcc56da7ec7880", + "type": "link in", + "z": "e43a27722b508115", + "name": "restart interface", + "links": [ + "fe3a855fee9e28c6" + ], + "x": 155, + "y": 600, + "wires": [ + [ + "d9d7acf381c002cf" + ] + ] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", @@ -9394,7 +9423,8 @@ "mode": "link", "links": [ "9bb0adbd716ce347", - "01c882fcc51b349c" + "01c882fcc51b349c", + "acfcc56da7ec7880" ], "x": 995, "y": 560, diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index d1dd847..58162e4 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -44,34 +44,39 @@ "filesize": 10396 }, "2": { + "src": "beta/OpenScanSettings.py", + "dst": "/usr/lib/python3/dist-packages/OpenScanSettings.py", + "filesize": 5293 + }, + "3": { "src": "beta/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", "filesize": 1288 }, - "3": { + "4": { "src": "beta/config.txt", "dst": "/boot/config.txt", - "filesize": 864 + "filesize": 866 }, - "4": { + "5": { "src": "beta/expand_root.sh", "dst": "/home/pi/OpenScan/files/expand_root.sh", "filesize": 170 }, - "5": { - "src": "meanwhile/fla.py", + "6": { + "src": "beta/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 17869 + "filesize": 18687 }, - "6": { + "7": { "src": "beta/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 342709 + "filesize": 346421 }, - "7": { + "8": { "src": "beta/settings.js", "dst": "/root/.node-red/settings.js", - "filesize": 21248 + "filesize": 21332 } }, "meanwhile": { @@ -83,7 +88,7 @@ "2": { "src": "meanwhile/OpenScanSettings.py", "dst": "/usr/lib/python3/dist-packages/OpenScanSettings.py", - "filesize": 5293 + "filesize": 5588 }, "3": { "src": "meanwhile/OpenScanStatistics.py", @@ -108,7 +113,7 @@ "7": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 346421 + "filesize": 347248 }, "8": { "src": "meanwhile/settings.js", From d7e57388b36447e0ed18daa0d70e52919d2ec93c Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 5 Oct 2024 21:58:53 +0200 Subject: [PATCH 31/38] zip file --- update/2024-1o/meanwhile/OpenScanSettings.py | 9 +++++---- update/2024-1o/meanwhile/fla.py | 2 +- update/2024-1o/update.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/update/2024-1o/meanwhile/OpenScanSettings.py b/update/2024-1o/meanwhile/OpenScanSettings.py index 7930873..1424985 100755 --- a/update/2024-1o/meanwhile/OpenScanSettings.py +++ b/update/2024-1o/meanwhile/OpenScanSettings.py @@ -115,9 +115,7 @@ class OpenScanSettings: @classmethod def get_openscan_settings(cls): - settings = { - "version": "1.0" # Add a version identifier - } + settings = {} blacklist = [ 'token', 'session_token', @@ -147,7 +145,10 @@ def get_openscan_settings(cls): except ValueError: print(f"Warning: Could not convert value for {field}. Skipping this field.") - return settings + return { + "version": "1.0", + "settings": settings + } @staticmethod def export_settings_to_file(settings, file_path=None): diff --git a/update/2024-1o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py index 2a94655..c0bfc29 100644 --- a/update/2024-1o/meanwhile/fla.py +++ b/update/2024-1o/meanwhile/fla.py @@ -126,7 +126,7 @@ def get(self): export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name + '.json') with ZipFile(openscan_tmp_folder + "/" + file_name + '.zip', 'w') as zip_object: - zip_object.write(openscan_tmp_folder + "/" + file_name + "json", zip_object.ZIP_DEFLATED) + zip_object.write(openscan_tmp_folder + "/" + file_name + ".json", arcname = file_name + '.json') if os.path.exists(openscan_tmp_folder + "/" + file_name + ".zip"): print("ZIP file created") else: diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index 58162e4..e07f5d2 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -108,7 +108,7 @@ "6": { "src": "meanwhile/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 18687 + "filesize": 18694 }, "7": { "src": "meanwhile/flows.json", From 45b6b9d878e6e24070bcffa4d8f4761322f1969f Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 5 Oct 2024 23:45:37 +0200 Subject: [PATCH 32/38] statistics endpoint --- .../2024-1o/meanwhile/OpenScanStatistics.py | 36 +++- update/2024-1o/meanwhile/fla.py | 35 +++- update/2024-1o/meanwhile/flows.json | 195 +++++++++++++++++- update/2024-1o/update.json | 6 +- 4 files changed, 258 insertions(+), 14 deletions(-) diff --git a/update/2024-1o/meanwhile/OpenScanStatistics.py b/update/2024-1o/meanwhile/OpenScanStatistics.py index 8adf9dd..caf31de 100755 --- a/update/2024-1o/meanwhile/OpenScanStatistics.py +++ b/update/2024-1o/meanwhile/OpenScanStatistics.py @@ -35,4 +35,38 @@ def write_statistics(self, scan_data: ScanData) -> None: # Append the new data as a new line with open(record_filename, "a") as json_file: json.dump(data, json_file, separators=(',', ':'), indent=None) # Collapsed JSON - json_file.write('\n') # Add a newline after each entry \ No newline at end of file + json_file.write('\n') # Add a newline after each entry + + def get_statistics_from_file(self): + ''' + get the required statistics as a dictionary + ''' + statistics = {} + file_path = self.filename + + # Check if the file exists + if not os.path.exists(file_path): + return statistics + + # Read all lines from the file + with open(file_path, 'r') as file: + lines = file.readlines() + + # Process each line (each JSON object) + for line in lines: + try: + data = json.loads(line.strip()) + for key, value in data.items(): + if key not in statistics: + statistics[key] = {} + if value not in statistics[key]: + statistics[key][value] = 0 + statistics[key][value] += 1 + except json.JSONDecodeError: + continue # Skip invalid JSON lines + + # Find the most common value for each field + for key in statistics: + statistics[key] = max(statistics[key], key=statistics[key].get) + + return statistics \ No newline at end of file diff --git a/update/2024-1o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py index c0bfc29..a445a79 100644 --- a/update/2024-1o/meanwhile/fla.py +++ b/update/2024-1o/meanwhile/fla.py @@ -5,7 +5,7 @@ from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time from OpenScan import load_int, load_float, load_bool, ringlight, motorrun -from OpenScanSettings import get_openscan_settings, export_settings_to_file +from OpenScanSettings import OpenScanSettings, get_openscan_settings, export_settings_to_file import RPi.GPIO as GPIO from math import sqrt import os @@ -14,6 +14,7 @@ import numpy as np from scipy import ndimage import socket +import zipfile GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) @@ -120,19 +121,42 @@ def get(self): @system_ns.route('/get_settings') class SendSettingsFile(Resource): def get(self): - openscan_tmp_folder = '/home/pi/OpenScan/tmp2' - file_name = 'settings' + statistics_folder:str = '/home/pi/OpenScan/statistics/' + openscan_tmp_folder:str = '/home/pi/OpenScan/tmp2' + file_name:str = 'settings' openscan_settings = get_openscan_settings() export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name + '.json') - with ZipFile(openscan_tmp_folder + "/" + file_name + '.zip', 'w') as zip_object: - zip_object.write(openscan_tmp_folder + "/" + file_name + ".json", arcname = file_name + '.json') + zip_path = os.path.join(openscan_tmp_folder, f"{file_name}.zip") + json_path = os.path.join(openscan_tmp_folder, f"{file_name}.json") + + with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as zip_object: + zip_object.write(json_path, arcname=f"{file_name}.json") + + if os.path.exists(statistics_folder): + for stat_file in os.listdir(statistics_folder): + file_path = os.path.join(statistics_folder, stat_file) + if os.path.isfile(file_path): + zip_object.write(file_path, f'statistics/{stat_file}') if os.path.exists(openscan_tmp_folder + "/" + file_name + ".zip"): print("ZIP file created") else: print("ZIP file not created") return send_from_directory(openscan_tmp_folder, file_name + ".zip", as_attachment=True) +@system_ns.route('/get_statistics') +class GetStatistics(Resource): + def get(self): + '''Get statistics from the OpenScanStatistics module''' + try: + from OpenScanStatistics import ScanStatistics + + stats = ScanStatistics() + statistics = stats.get_statistics_from_file() + + return {'statistics': statistics}, 200 + except Exception as e: + return {'error': f'Error retrieving statistics: {str(e)}'}, 500 @system_ns.route('/shutdown') class Shutdown(Resource): @@ -533,4 +557,3 @@ def favicon(): if __name__ == '__main__': app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) - diff --git a/update/2024-1o/meanwhile/flows.json b/update/2024-1o/meanwhile/flows.json index 3a4336e..e9c4a76 100644 --- a/update/2024-1o/meanwhile/flows.json +++ b/update/2024-1o/meanwhile/flows.json @@ -9775,7 +9775,7 @@ "order": 8, "width": "3", "height": 1, - "format": "\n\n \n\n\n\n", + "format": "\n
Upload Config
\n
\n\n\n\n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": false, @@ -9785,7 +9785,7 @@ "y": 1060, "wires": [ [ - "5e18b80e617a3db8" + "f92f5fe65eaea996" ] ] }, @@ -9801,8 +9801,8 @@ "complete": "false", "statusVal": "", "statusType": "auto", - "x": 750, - "y": 1060, + "x": 790, + "y": 1000, "wires": [] }, { @@ -9822,6 +9822,193 @@ "y": 380, "wires": [] }, + { + "id": "f92f5fe65eaea996", + "type": "file", + "z": "a5557543ccff5889", + "name": "Save Settings", + "filename": "/home/pi/OpenScan/tmp2/upload.zip", + "filenameType": "str", + "appendNewline": false, + "createDir": false, + "overwriteFile": "true", + "encoding": "binary", + "x": 700, + "y": 1060, + "wires": [ + [] + ] + }, + { + "id": "c4f91df3.caef7", + "type": "http in", + "z": "a5557543ccff5889", + "name": "", + "url": "/files", + "method": "post", + "upload": true, + "swaggerDoc": "", + "x": 240, + "y": 1280, + "wires": [ + [ + "cc5a37cf.86c6d8", + "c14febc0.522db8" + ] + ] + }, + { + "id": "ef0aaf76.2236e", + "type": "http response", + "z": "a5557543ccff5889", + "name": "", + "statusCode": "", + "headers": {}, + "x": 590, + "y": 1280, + "wires": [] + }, + { + "id": "cc5a37cf.86c6d8", + "type": "debug", + "z": "a5557543ccff5889", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "req", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 400, + "y": 1320, + "wires": [] + }, + { + "id": "6a156025.b1c9f", + "type": "inject", + "z": "a5557543ccff5889", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 240, + "y": 1380, + "wires": [ + [ + "fd62708f.a560a" + ] + ] + }, + { + "id": "9c0b2081.b1ac6", + "type": "function", + "z": "a5557543ccff5889", + "name": "Format the header and payload", + "func": "msg.headers = {\n \"Content-Type\": \"multipart/form-data; boundary=------------------------d74496d66958873e\"\n}\n\n\nmsg.payload = '--------------------------d74496d66958873e\\r\\n'+\n'Content-Disposition: form-data; name=\"select\"\\r\\n'+\n'\\r\\n'+\n'true\\r\\n'+\n'--------------------------d74496d66958873e\\r\\n'+\n'Content-Disposition: form-data; name=\"print\"\\r\\n'+\n'\\r\\n'+\n'true\\r\\n'+\n'--------------------------d74496d66958873e\\r\\n'+\n'Content-Disposition: form-data; name=\"file\"; filename=\"'+msg.filename+'\"\\r\\n'+\n'Content-Type: application/octet-stream\\r\\n'+\n'\\r\\n'+\nmsg.payload+'\\r\\n'+\n'--------------------------d74496d66958873e--\\r\\n';\n\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 370, + "y": 1440, + "wires": [ + [ + "85c748e8.2d1f88" + ] + ] + }, + { + "id": "85c748e8.2d1f88", + "type": "http request", + "z": "a5557543ccff5889", + "name": "", + "method": "POST", + "ret": "txt", + "paytoqs": false, + "url": "http://localhost:1880/files", + "tls": "", + "proxy": "", + "x": 610, + "y": 1440, + "wires": [ + [ + "a4810d7a.f652a" + ] + ] + }, + { + "id": "a4810d7a.f652a", + "type": "debug", + "z": "a5557543ccff5889", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "x": 790, + "y": 1440, + "wires": [] + }, + { + "id": "c14febc0.522db8", + "type": "change", + "z": "a5557543ccff5889", + "name": "", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "okay", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 420, + "y": 1280, + "wires": [ + [ + "ef0aaf76.2236e" + ] + ] + }, + { + "id": "fd62708f.a560a", + "type": "file in", + "z": "a5557543ccff5889", + "name": "", + "filename": "/tmp/file.txt", + "filenameType": "str", + "format": "", + "chunk": false, + "sendError": false, + "allProps": false, + "x": 420, + "y": 1380, + "wires": [ + [ + "9c0b2081.b1ac6" + ] + ] + }, { "id": "9b3e6a06c82a0f52", "type": "link in", diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index e07f5d2..3bf8b5f 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -66,7 +66,7 @@ "6": { "src": "beta/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 18687 + "filesize": 19750 }, "7": { "src": "beta/flows.json", @@ -93,7 +93,7 @@ "3": { "src": "meanwhile/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", - "filesize": 1288 + "filesize": 2446 }, "4": { "src": "meanwhile/config.txt", @@ -113,7 +113,7 @@ "7": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 347248 + "filesize": 352950 }, "8": { "src": "meanwhile/settings.js", From fd5f6c4e949bd3cedfb20d708ece1af994eaa755 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sat, 5 Oct 2024 23:55:02 +0200 Subject: [PATCH 33/38] statistics --- .../2024-1o/meanwhile/OpenScanStatistics.py | 30 ++++++++++--------- update/2024-1o/update.json | 4 +-- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/update/2024-1o/meanwhile/OpenScanStatistics.py b/update/2024-1o/meanwhile/OpenScanStatistics.py index caf31de..8b40088 100755 --- a/update/2024-1o/meanwhile/OpenScanStatistics.py +++ b/update/2024-1o/meanwhile/OpenScanStatistics.py @@ -42,28 +42,30 @@ def get_statistics_from_file(self): get the required statistics as a dictionary ''' statistics = {} - file_path = self.filename + directory = self.filename - # Check if the file exists - if not os.path.exists(file_path): + # Check if the directory exists + if not os.path.exists(directory): return statistics - # Read all lines from the file - with open(file_path, 'r') as file: - lines = file.readlines() - - # Process each line (each JSON object) - for line in lines: - try: - data = json.loads(line.strip()) - for key, value in data.items(): + # Process all CSV files in the directory + for filename in os.listdir(directory): + if filename.endswith('.csv'): + file_path = os.path.join(directory, filename) + with open(file_path, 'r') as file: + lines = file.readlines() + + # Process each line of the CSV file + for line in lines[1:]: # Skip header row + data = line.strip().split(',') + if len(data) < 2: # Ensure there's at least a key-value pair + continue + key, value = data[0], data[1] if key not in statistics: statistics[key] = {} if value not in statistics[key]: statistics[key][value] = 0 statistics[key][value] += 1 - except json.JSONDecodeError: - continue # Skip invalid JSON lines # Find the most common value for each field for key in statistics: diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index 3bf8b5f..b103cf8 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -93,7 +93,7 @@ "3": { "src": "meanwhile/OpenScanStatistics.py", "dst": "/usr/lib/python3/dist-packages/OpenScanStatistics.py", - "filesize": 2446 + "filesize": 2682 }, "4": { "src": "meanwhile/config.txt", @@ -108,7 +108,7 @@ "6": { "src": "meanwhile/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 18694 + "filesize": 19750 }, "7": { "src": "meanwhile/flows.json", From 83e95e0ebfd182b02f1931c3b4fadd18090634e0 Mon Sep 17 00:00:00 2001 From: Stealth Date: Sun, 6 Oct 2024 11:00:55 +0200 Subject: [PATCH 34/38] settings --- update/2024-1o/meanwhile/OpenScanSettings.py | 14 +++++--------- update/2024-1o/meanwhile/fla.py | 4 +++- update/2024-1o/scripts/expand_root.sh | 2 +- update/2024-1o/update.json | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/update/2024-1o/meanwhile/OpenScanSettings.py b/update/2024-1o/meanwhile/OpenScanSettings.py index 1424985..45e7e4f 100755 --- a/update/2024-1o/meanwhile/OpenScanSettings.py +++ b/update/2024-1o/meanwhile/OpenScanSettings.py @@ -7,7 +7,6 @@ @dataclass class OpenScanSettings: advanced_settings: bool - architecture: str cam_awbg_blue: int cam_awbg_red: int cam_contrast: int @@ -35,7 +34,6 @@ class OpenScanSettings: cam_shutter: int cam_stacksize: int cam_timeout: int - datadog_api_token: str datadog_enable: bool delete_aborted: bool diskspace_threshold: int @@ -50,9 +48,7 @@ class OpenScanSettings: interface_color: str model: str object_size: float - openscan_branch: str openscan_uuid: str - openscan_version: str osc_credit: int osc_limit_filesize: int osc_limit_photos: int @@ -69,7 +65,6 @@ class OpenScanSettings: pin_rotor_endstop: int pin_rotor_step: int pin_tt_dir: int - pin_tt_enable: int pin_tt_step: int raspberry_model: str raspbian_codename: str @@ -91,17 +86,14 @@ class OpenScanSettings: routine_secondpass: bool sdcard_manfid: str sdcard_name: str - session_token: str shield_type: str smb_enable: bool ssh_enable: bool status_cloud: str status_internal_cam: str - telegram_api_token: str telegram_client_id: str telegram_enable: bool terms: bool - token: str tt_acc: float tt_accramp: int tt_angle: int @@ -117,6 +109,9 @@ class OpenScanSettings: def get_openscan_settings(cls): settings = {} blacklist = [ + 'architecture', + 'openscan_version', + 'openscan_branch', 'token', 'session_token', 'telegram_api_token', @@ -124,7 +119,8 @@ def get_openscan_settings(cls): 'status_uploadprogress', 'raspberry_model', 'sdcard_name', - 'sdcard_manfid' + 'sdcard_manfid', + 'uploadprogress' ] # Add more keywords as needed for field in cls.__dataclass_fields__: if field not in blacklist: diff --git a/update/2024-1o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py index a445a79..b32751d 100644 --- a/update/2024-1o/meanwhile/fla.py +++ b/update/2024-1o/meanwhile/fla.py @@ -2,6 +2,7 @@ from flask import Flask, request, redirect, send_file, send_from_directory from flask_restx import Resource, Api, Namespace from picamera2 import Picamera2 +from datetime import datetime from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time from OpenScan import load_int, load_float, load_bool, ringlight, motorrun @@ -123,7 +124,8 @@ class SendSettingsFile(Resource): def get(self): statistics_folder:str = '/home/pi/OpenScan/statistics/' openscan_tmp_folder:str = '/home/pi/OpenScan/tmp2' - file_name:str = 'settings' + current_date = datetime.now().strftime("%Y-%m-%d") + file_name = f"openscan-settings-{current_date}.json" openscan_settings = get_openscan_settings() export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name + '.json') diff --git a/update/2024-1o/scripts/expand_root.sh b/update/2024-1o/scripts/expand_root.sh index f4f7148..5f57a36 100755 --- a/update/2024-1o/scripts/expand_root.sh +++ b/update/2024-1o/scripts/expand_root.sh @@ -1,5 +1,5 @@ #!/bin/bash -if test -f "/boot/expand_root"; then +if [ "$1" = "-f" ] || test -f "/boot/expand_root"; then echo "expanding root partition" raspi-config --expand-rootfs rm -fr /boot/expand_root diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index b103cf8..ed60ac7 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -66,7 +66,7 @@ "6": { "src": "beta/fla.py", "dst": "/home/pi/OpenScan/files/fla.py", - "filesize": 19750 + "filesize": 19865 }, "7": { "src": "beta/flows.json", @@ -88,7 +88,7 @@ "2": { "src": "meanwhile/OpenScanSettings.py", "dst": "/usr/lib/python3/dist-packages/OpenScanSettings.py", - "filesize": 5588 + "filesize": 5520 }, "3": { "src": "meanwhile/OpenScanStatistics.py", From 28522b8a3ac346a2f3e54832390a9aa5193f5576 Mon Sep 17 00:00:00 2001 From: Stealth Date: Thu, 10 Oct 2024 19:35:38 +0200 Subject: [PATCH 35/38] user bugfixes --- update/2024-1o/meanwhile/config.txt | 2 +- update/2024-1o/meanwhile/fla.py | 4 +--- update/2024-1o/meanwhile/flows.json | 2 +- update/2024-1o/meanwhile/settings.js | 3 ++- update/2024-1o/update.json | 6 +++--- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/update/2024-1o/meanwhile/config.txt b/update/2024-1o/meanwhile/config.txt index f601472..953df5f 100755 --- a/update/2024-1o/meanwhile/config.txt +++ b/update/2024-1o/meanwhile/config.txt @@ -26,7 +26,7 @@ otg_mode=1 [pi4] # Run as fast as firmware / board allows arm_boost=1 -dtoverlay=imx519,cma-512 +#dtoverlay=imx519,cma-512 [all] camera_auto_detect=0 diff --git a/update/2024-1o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py index b32751d..a445a79 100644 --- a/update/2024-1o/meanwhile/fla.py +++ b/update/2024-1o/meanwhile/fla.py @@ -2,7 +2,6 @@ from flask import Flask, request, redirect, send_file, send_from_directory from flask_restx import Resource, Api, Namespace from picamera2 import Picamera2 -from datetime import datetime from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont from time import sleep, time from OpenScan import load_int, load_float, load_bool, ringlight, motorrun @@ -124,8 +123,7 @@ class SendSettingsFile(Resource): def get(self): statistics_folder:str = '/home/pi/OpenScan/statistics/' openscan_tmp_folder:str = '/home/pi/OpenScan/tmp2' - current_date = datetime.now().strftime("%Y-%m-%d") - file_name = f"openscan-settings-{current_date}.json" + file_name:str = 'settings' openscan_settings = get_openscan_settings() export_settings_to_file(openscan_settings, openscan_tmp_folder + "/" + file_name + '.json') diff --git a/update/2024-1o/meanwhile/flows.json b/update/2024-1o/meanwhile/flows.json index e9c4a76..964d1b7 100644 --- a/update/2024-1o/meanwhile/flows.json +++ b/update/2024-1o/meanwhile/flows.json @@ -9775,7 +9775,7 @@ "order": 8, "width": "3", "height": 1, - "format": "\n
Upload Config
\n
\n\n\n\n", + "format": "\n \n
Upload Config
\n
\n\n \n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": false, diff --git a/update/2024-1o/meanwhile/settings.js b/update/2024-1o/meanwhile/settings.js index b71b7f3..b707922 100644 --- a/update/2024-1o/meanwhile/settings.js +++ b/update/2024-1o/meanwhile/settings.js @@ -233,7 +233,8 @@ userDir: '/home/pi/OpenScan/settings/.node-red/', // {path: '/home/nol/reports/', root: "/doc/"}, //], httpStatic: [ - {path: '/home/pi/OpenScan/tmp2/', root: "/tmp2/"} + {path: '/home/pi/OpenScan/tmp2/', root: "/tmp2/"}, + {path: '/home/pi/OpenScan/scans/', root: "/scans/"} ], /** diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index ed60ac7..b1858ad 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -98,7 +98,7 @@ "4": { "src": "meanwhile/config.txt", "dst": "/boot/config.txt", - "filesize": 866 + "filesize": 867 }, "5": { "src": "meanwhile/expand_root.sh", @@ -113,12 +113,12 @@ "7": { "src": "meanwhile/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 352950 + "filesize": 353170 }, "8": { "src": "meanwhile/settings.js", "dst": "/root/.node-red/settings.js", - "filesize": 21332 + "filesize": 21386 } } } \ No newline at end of file From b638ed5f2de9c24298771d8373b1bf8a7ff062d4 Mon Sep 17 00:00:00 2001 From: Unus-Multorum Date: Sun, 13 Oct 2024 21:03:18 +0200 Subject: [PATCH 36/38] Endstops enabled Added back endstop functionalty --- update/2024-1o/meanwhile/OpenScan.py | 32 +-- update/2024-1o/meanwhile/flows.json | 401 ++++++++++++++++----------- 2 files changed, 261 insertions(+), 172 deletions(-) diff --git a/update/2024-1o/meanwhile/OpenScan.py b/update/2024-1o/meanwhile/OpenScan.py index cbce61c..f5ac509 100644 --- a/update/2024-1o/meanwhile/OpenScan.py +++ b/update/2024-1o/meanwhile/OpenScan.py @@ -131,7 +131,7 @@ def camera(cmd, msg = {}): except: return 400 -def motorrun(motor,angle,ES_enable=False,ES_start_state = True): +def motorrun(motor,angle,endstop=False): #motor can be "rotor", "tt" or "extra" import RPi.GPIO as GPIO from time import sleep @@ -144,22 +144,20 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): spr = load_int(motor + '_stepsperrotation') dirpin = load_int('pin_' + motor + '_dir') steppin = load_int('pin_' + motor +'_step') - ES_pin = load_int('pin_' + motor + '_endstop') dir = load_int(motor + '_dir') ramp = load_int(motor + '_accramp') acc = load_float(motor + '_acc') - if motor != 'tt': - ES_pin = load_int('pin_' + motor + '_endstop') - else: - ES_pin = '33' delay_init = load_float(motor + '_delay') delay = delay_init step_count=int(angle*spr/360) * dir GPIO.setup(dirpin, GPIO.OUT) GPIO.setup(steppin, GPIO.OUT) - if motor != 'tt': - GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) + + if endstop: + endstop_pin = load_int('pin_' + motor + '_endstop') + endstop_pushed = load_bool(motor + '_endstop_pushed') + GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) if (step_count>0): GPIO.output(dirpin, GPIO.HIGH) @@ -167,14 +165,16 @@ def motorrun(motor,angle,ES_enable=False,ES_start_state = True): GPIO.output(dirpin, GPIO.LOW) step_count=-step_count for x in range(step_count): - if ES_enable == True and GPIO.input(ES_pin) != ES_start_state and motor != 'tt': - i = 0 - while i <= 10: - if GPIO.input(ES_pin) == ES_start_state: - i = 11 - if i == 10: - return - i = i + 1 + if endstop: + # Stop movement if endstop is pushed AND if rotor is moving and isn't going away from the endstop. + if GPIO.input(endstop_pin) == endstop_pushed and (motor == 'rotor' and GPIO.input(dirpin) == False): + i = 0 + while i <= 10: + if GPIO.input(endstop_pin) != endstop_pushed: + i = 11 + if i == 10: + return + i = i + 1 GPIO.output(steppin, GPIO.HIGH) if x<=ramp and x<=step_count/2: diff --git a/update/2024-1o/meanwhile/flows.json b/update/2024-1o/meanwhile/flows.json index 964d1b7..1c5bbc8 100644 --- a/update/2024-1o/meanwhile/flows.json +++ b/update/2024-1o/meanwhile/flows.json @@ -1771,7 +1771,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "motorrun", - "func": "from OpenScan import motorrun, load_int\n\nif 'payload' not in msg:\n return\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'))\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'))\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", + "func": "from OpenScan import motorrun, load_int, load_bool\n\nif 'payload' not in msg:\n return\n\nrotor_endstop_enable = load_bool('rotor_endstop_enable')\n\nif msg['payload'] == \"up\":\n motorrun('rotor',load_int('rotor_angle'), rotor_endstop_enable)\nif msg['payload'] == \"down\":\n motorrun('rotor',-load_int('rotor_angle'), rotor_endstop_enable)\nif msg['payload'] == \"left\":\n motorrun('tt',load_int('tt_angle'))\nif msg['payload'] == \"right\":\n motorrun('tt',-load_int('tt_angle'))\n\n", "outputs": 1, "x": 780, "y": 840, @@ -2268,7 +2268,7 @@ "type": "python3-function", "z": "481edaf6db5a7a54", "name": "Routine", - "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics, ScanData\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"openscan_branch\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nendstop_enable = load_bool('rotor_enable_endstop')\nif endstop_enable:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True, False)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle)\n else:\n motorrun('rotor',rotor_angle)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0] )\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\n\nif counter == photocount:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n aborted=False\nelse:\n aborted=True\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nscan_data = ScanData(\n arch=architecture,\n openscan_version=openscan_version,\n openscan_branch=openscan_branch,\n shield=shield,\n date_init=date_init,\n date_end=date_end,\n num_photos=photocount,\n done_photos=counter,\n camera=camera_model,\n stack_size=stacksize,\n telegram_enabled=telegram_enable,\n delete_aborted=delete_aborted,\n endstop_enabled=endstop_enable,\n group_stack_photos=group_stack_photos,\n aborted=aborted\n)\n\nstats.write_statistics(scan_data)\nreturn msg\n", + "func": "# The contents of this file are embedded in the OpenScan app (Node-RED)\nfrom OpenScan import load_bool, load_str, load_int, load_float, motorrun, sort_spherical_coordinates_deg, create_coordinates, take_photo, save, \\\n load_bool, camera\nfrom OpenScanStatistics import ScanStatistics, ScanData\nfrom time import sleep, strftime, time\nfrom subprocess import getoutput, run\nfrom datetime import datetime\nfrom zipfile import ZipFile, ZIP_DEFLATED\nfrom os import system, uname, remove\nfrom os.path import isfile, getsize\nimport math\nimport threading\nimport numpy as np\nimport json\n\nif load_str(\"status_internal_cam\") == \"no camera found\" or load_str(\"status_internal_cam\")[:5] == \"Featu\":\n return\n\nstats = ScanStatistics()\n\nsave('status_internal_cam', 'Routine-preparing')\ncamera('/v1/camera/picam2_switch_mode?mode=1')\nsave('cam_sharparea', False)\nsave('cam_features', False)\n\narchitecture = load_str(\"architecture\")\nopenscan_version = load_str(\"openscan_version\")\nopenscan_branch = load_str(\"openscan_branch\")\ncamera_model = load_str(\"camera\")\nshield = load_str(\"shield_type\")\nprojectname = load_str(\"routine_projectname\")\nangle_max = load_int('rotor_anglemax')\nangle_min = load_int('rotor_anglemin')\ndelete_aborted = load_bool('delete_aborted')\nrotate_tt_first = load_bool('rotate_tt_first')\nrotor_endstop_enable = load_bool('rotor_endstop_enable')\nif rotor_endstop_enable:\n angle_start = load_int('rotor_endstop_angle')\n motorrun('rotor',angle_start/abs(angle_start) * 130, True)\n\nelse:\n angle_start = load_int('rotor_anglestart')\n\n\nphotocount = load_int('routine_photocount')\n\nfocus_min = load_float('cam_focus_min')\nfocus_max = load_float('cam_focus_max')\nstacksize = load_int('cam_stacksize')\ngroup_stack_photos = load_bool('group_stack_photos')\n\ntelegram_enable = load_bool('telegram_enable')\nif telegram_enable:\n telegram_api_token = load_str('telegram_api_token')\n telegram_client_id = load_str('telegram_client_id')\n\nif focus_min == focus_max:\n stacksize = 1\n\nfocuslist = []\nif stacksize == 1:\n steps = 3 + int(abs(focus_max-focus_min)*0.8)\nelse:\n steps = stacksize\n\nfor i in range (steps):\n focuslist.append(min(focus_min,focus_max) + i * abs(focus_max-focus_min)/(steps-1))\n\nmsg['focuslist'] = focuslist\nmsg['payload2'] = []\ncounter = 0\n\nbasepath = '/home/pi/OpenScan/'\ntemppath = basepath + 'tmp2/preview.jpg'\nzippath = basepath + 'tmp.zip'\n\nprojectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname\n\nif isfile(zippath):\n system('rm ' + zippath)\nsleep(1)\n\ncoordinates = create_coordinates(angle_min, angle_max, photocount)\ncoordinates = sort_spherical_coordinates_deg(coordinates)\n\nmsg['payload'] = coordinates\n\nposition_last = (angle_start, 0)\n\nzip = ZipFile(zippath, \"a\", ZIP_DEFLATED, allowZip64=True)\n\nhostname = str(uname()[1])\n\nstarttime = time()\n\ndef get_current_timestamp():\n return datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n\ndef get_eta(starttime, photocounter, count):\n return str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n\ndef photo(counter2):\n camera('/v1/camera/picam2_take_photo')\n returning[0] = focus(returning[0])\n zip.write(temppath, projectname + '_' + str(counter) + \".jpg\")\n\ndef stack_photo(i):\n \n camera('/v1/camera/picam2_take_photo')\n if group_stack_photos:\n name = projectname + '_' + str(counter) + \"/\" + projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n else:\n name = projectname + '_' + str(counter) + '-' + str(i) + '.jpg'\n zip.write(temppath, name)\n \ndef stack_focus(i):\n sleep(load_float('cam_shutter')/1000000*2)\n if i < len(focuslist)-1:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[i+1]))\n else:\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n sleep(1.7)\n\ndef photo_stack():\n camera('/v1/camera/picam2_focus?focus=' + str(focuslist[0]))\n for i in range(len(focuslist)):\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + \"-F\"+ str(i+1))\n \n focus_thread = threading.Thread(target=stack_focus, args=(i,))\n photo_thread = threading.Thread(target=stack_photo, args=(i,))\n \n focus_thread.start()\n photo_thread.start()\n \n focus_thread.join()\n photo_thread.join()\n\ndef move_motor():\n rotor_angle = position[0] - position_last[0]\n msg['payload2'].append(rotor_angle)\n #if abs(rotor_angle) > 180:\n # rotor_angle = -360 * rotor_angle / abs(rotor_angle) + rotor_angle\n tt_angle = position_last[1] - position[1]\n if tt_angle > 180:\n tt_angle -= 360\n elif tt_angle < -180:\n tt_angle += 360\n \n if rotate_tt_first:\n motorrun('tt',tt_angle)\n motorrun('rotor',rotor_angle, rotor_endstop_enable)\n else:\n motorrun('rotor',rotor_angle, rotor_endstop_enable)\n motorrun('tt',tt_angle)\n return\n\ndef check_diskspace():\n diskspace_threshold = load_int('diskspace_threshold')\n diskspace = getoutput('df -h / | awk \"{print $5}\"').split('\\n')[1]\n available = int(float(diskspace.replace(' ','').split('G')[2])*1000)\n if available < diskspace_threshold:\n save('status_internal_cam', 'Routine-stopping')\n return\n\ndef focus(i):\n f = focuslist[i]\n camera('/v1/camera/picam2_focus?focus=' + str(f))\n if i < len(focuslist) - 1:\n i += 1\n else:\n i = 0\n return i\n\ndef send_telegram_message(message, telegram_api_token, telegram_client_id):\n telegram_bot_path = '/usr/local/bin/send-telegram'\n run([telegram_bot_path,\"-a\",telegram_api_token,\"-c\",telegram_client_id,\"-m\",message])\n\ncounter2 = 0\n\ndate_init = get_current_timestamp()\n\nif telegram_enable:\n telegram_message = \"[START] \" + hostname + \" starting \" + projectname + \"(\" + str(photocount) + \" photos) ETA: \"\n try:\n send_telegram_message(telegram_message, telegram_api_token, telegram_client_id)\n except Exception as e:\n print(e)\n\nfor position in coordinates:\n counter += 1\n filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + \".jpg\"\n if load_str('status_internal_cam') == \"Routine-stopping\":\n break\n if counter < 6:\n ETA = ''\n\n save('status_internal_cam', 'Routine-Photo ' + str(counter) + '/' + str(photocount) + ETA)\n if counter > 6:\n check_diskspace()\n\n move_motor()\n sleep(load_float(\"cam_delay_before\"))\n \n if stacksize ==1:\n returning = [counter2]\n photo(returning)\n counter2 = returning[0]\n else:\n photo_stack()\n\n sleep(load_float(\"cam_delay_after\"))\n ETA = '-ETA:' + str(int((photocount / counter - 1) * (time() - starttime))) + '/' + str(\n int(photocount / counter * (time() - starttime))) + 's'\n \n # Calculate time remaining and write status to /tmp/status as JSON\n elapsed_time = time() - starttime\n estimated_total_time = (elapsed_time / counter) * photocount * steps\n time_remaining = max(0, estimated_total_time - elapsed_time)\n \n status = {\n \"scan_name\": projectname,\n \"total_photos\": photocount,\n \"current_photo\": counter,\n \"stacksize\": steps,\n \"start_time\": int(starttime),\n }\n with open('/tmp/status.json', 'w') as status_file:\n json.dump(status, status_file)\n\n position_last = position\n\nzip.close()\ntry:\n send_telegram_message(\"[STOP] \" + hostname + \" stop \" + projectname, telegram_api_token, telegram_client_id)\nexcept Exception as e:\n print(e)\ncamera('/v1/camera/picam2_switch_mode?mode=0')\n\nsave('status_internal_cam', 'Routine-done')\n\ntry:\n remove('/tmp/status.json')\nexcept FileNotFoundError:\n pass # File doesn't exist, so no need to delete\nexcept Exception as e:\n print(f\"Error deleting /tmp/status.json: {e}\")\n\nmotorrun('rotor', -position_last[0], rotor_endstop_enable)\nmotorrun('tt', position_last[1])\n\nsave('status_internal_cam', '--READY--')\ndate_end = get_current_timestamp()\n\n\nif counter == photocount:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n aborted=False\nelse:\n aborted=True\n if delete_aborted:\n remove(zippath)\n else:\n system('mv ' + zippath + \" \" + basepath + \"scans/\" + projectcode + \".zip\")\n\nscan_data = ScanData(\n arch=architecture,\n openscan_version=openscan_version,\n openscan_branch=openscan_branch,\n shield=shield,\n date_init=date_init,\n date_end=date_end,\n num_photos=photocount,\n done_photos=counter,\n camera=camera_model,\n stack_size=stacksize,\n telegram_enabled=telegram_enable,\n delete_aborted=delete_aborted,\n endstop_enabled=rotor_endstop_enable,\n group_stack_photos=group_stack_photos,\n aborted=aborted\n)\n\nstats.write_statistics(scan_data)\nreturn msg\n", "outputs": 1, "x": 300, "y": 1040, @@ -4769,7 +4769,7 @@ "name": "Camera", "info": "", "x": 90, - "y": 2500, + "y": 2560, "wires": [] }, { @@ -4779,7 +4779,7 @@ "name": "Pinout", "info": "", "x": 90, - "y": 2960, + "y": 3020, "wires": [] }, { @@ -4790,7 +4790,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 16, + "order": 18, "width": 3, "height": 1, "passthru": false, @@ -4802,7 +4802,7 @@ "step": "0.005", "className": "", "x": 450, - "y": 2100, + "y": 2160, "wires": [ [ "11fd3363416433f9" @@ -4829,7 +4829,7 @@ "step": "0.005", "className": "", "x": 420, - "y": 2340, + "y": 2400, "wires": [ [ "e50492d1e18f43c6" @@ -4844,7 +4844,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 18, + "order": 20, "width": 3, "height": 1, "passthru": false, @@ -4856,7 +4856,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2140, + "y": 2200, "wires": [ [ "e8b24efb0f30288e" @@ -4871,7 +4871,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 20, + "order": 22, "width": 3, "height": 1, "passthru": false, @@ -4883,7 +4883,7 @@ "step": "100", "className": "", "x": 440, - "y": 2180, + "y": 2240, "wires": [ [ "29f576be9e292232" @@ -4898,7 +4898,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 14, + "order": 16, "width": 3, "height": 1, "passthru": false, @@ -4909,7 +4909,7 @@ "className": "", "topicType": "msg", "x": 460, - "y": 2060, + "y": 2120, "wires": [ [ "78e256083f59f66f" @@ -4921,7 +4921,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 17, + "order": 19, "width": 3, "height": 1, "name": "rotor_acc_label", @@ -4930,7 +4930,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 2140, + "y": 2200, "wires": [] }, { @@ -4938,7 +4938,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 19, + "order": 21, "width": 3, "height": 1, "name": "rotor_accramp_label", @@ -4947,7 +4947,7 @@ "layout": "row-spread", "className": "", "x": 800, - "y": 2180, + "y": 2240, "wires": [] }, { @@ -4955,7 +4955,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 15, + "order": 17, "width": 3, "height": 1, "name": "rotor_delay_label", @@ -4964,7 +4964,7 @@ "layout": "row-left", "className": "", "x": 790, - "y": 2100, + "y": 2160, "wires": [] }, { @@ -4972,7 +4972,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 13, + "order": 15, "width": 3, "height": 1, "name": "rotor_steps_rotation_label", @@ -4981,7 +4981,7 @@ "layout": "row-left", "className": "", "x": 810, - "y": 2060, + "y": 2120, "wires": [] }, { @@ -4998,7 +4998,7 @@ "layout": "row-center", "className": "", "x": 90, - "y": 2300, + "y": 2360, "wires": [] }, { @@ -5021,7 +5021,7 @@ "step": "0.1", "className": "", "x": 410, - "y": 2380, + "y": 2440, "wires": [ [ "af88b9da72917d62" @@ -5048,7 +5048,7 @@ "step": "1", "className": "", "x": 430, - "y": 2420, + "y": 2480, "wires": [ [ "b1b4678827d3a6dd" @@ -5074,7 +5074,7 @@ "className": "", "topicType": "msg", "x": 450, - "y": 2300, + "y": 2360, "wires": [ [ "eef89545ec0f6aa8" @@ -5095,7 +5095,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 2420, + "y": 2480, "wires": [] }, { @@ -5112,7 +5112,7 @@ "layout": "row-spread", "className": "", "x": 810, - "y": 2300, + "y": 2360, "wires": [] }, { @@ -5129,7 +5129,7 @@ "layout": "row-left", "className": "", "x": 770, - "y": 2380, + "y": 2440, "wires": [] }, { @@ -5146,7 +5146,7 @@ "layout": "row-left", "className": "", "x": 780, - "y": 2340, + "y": 2400, "wires": [] }, { @@ -5157,7 +5157,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 22, + "order": 24, "width": 3, "height": 1, "passthru": false, @@ -5169,7 +5169,7 @@ "step": "1", "className": "", "x": 430, - "y": 2220, + "y": 2280, "wires": [ [ "c4b5a38c5c1df3d2" @@ -5181,7 +5181,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 21, + "order": 23, "width": 3, "height": 1, "name": "rotor_angle_label", @@ -5190,7 +5190,7 @@ "layout": "row-spread", "className": "", "x": 790, - "y": 2220, + "y": 2280, "wires": [] }, { @@ -5213,7 +5213,7 @@ "step": "1", "className": "", "x": 420, - "y": 2460, + "y": 2520, "wires": [ [ "0f3367983bb8e159" @@ -5234,7 +5234,7 @@ "layout": "row-spread", "className": "", "x": 780, - "y": 2460, + "y": 2520, "wires": [] }, { @@ -5274,7 +5274,7 @@ "step": "1", "className": "", "x": 410, - "y": 2500, + "y": 2560, "wires": [ [ "c9d2e31514def4fc" @@ -5289,7 +5289,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 24, + "order": 26, "width": 3, "height": 1, "passthru": false, @@ -5301,7 +5301,7 @@ "step": "1", "className": "", "x": 420, - "y": 2260, + "y": 2320, "wires": [ [ "523717b0f218a5fd" @@ -5322,7 +5322,7 @@ "layout": "row-spread", "className": "", "x": 770, - "y": 2500, + "y": 2560, "wires": [] }, { @@ -5330,7 +5330,7 @@ "type": "ui_text", "z": "e43a27722b508115", "group": "7a3279eea439bcdd", - "order": 23, + "order": 25, "width": 3, "height": 1, "name": "rotor_dir_label", @@ -5339,7 +5339,7 @@ "layout": "row-spread", "className": "", "x": 780, - "y": 2260, + "y": 2320, "wires": [] }, { @@ -5391,7 +5391,7 @@ "step": "0.02", "className": "", "x": 450, - "y": 2600, + "y": 2660, "wires": [ [ "5c752757090c49d2" @@ -5418,7 +5418,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2640, + "y": 2700, "wires": [ [ "a1769f0277834f6d" @@ -5445,7 +5445,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2760, + "y": 2820, "wires": [ [ "1a8b0ba21b4f3005", @@ -5473,7 +5473,7 @@ "step": "0.1", "className": "", "x": 420, - "y": 2800, + "y": 2860, "wires": [ [ "dc8fc962ff7d594b", @@ -5501,7 +5501,7 @@ "step": "1", "className": "", "x": 410, - "y": 2840, + "y": 2900, "wires": [ [ "00e7836ccb3c4d0c" @@ -5522,7 +5522,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2600, + "y": 2660, "wires": [] }, { @@ -5539,7 +5539,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2640, + "y": 2700, "wires": [] }, { @@ -5556,7 +5556,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2760, + "y": 2820, "wires": [] }, { @@ -5573,7 +5573,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2800, + "y": 2860, "wires": [] }, { @@ -5590,7 +5590,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 2840, + "y": 2900, "wires": [] }, { @@ -5612,7 +5612,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3000, + "y": 3060, "wires": [ [ "885bc559fafec5f2" @@ -5633,7 +5633,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3000, + "y": 3060, "wires": [] }, { @@ -5655,7 +5655,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3040, + "y": 3100, "wires": [ [ "f70321c96bf81360" @@ -5676,7 +5676,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3040, + "y": 3100, "wires": [] }, { @@ -5698,7 +5698,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3080, + "y": 3140, "wires": [ [ "95e1603bbd06a69d" @@ -5719,7 +5719,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3080, + "y": 3140, "wires": [] }, { @@ -5741,7 +5741,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3120, + "y": 3180, "wires": [ [ "a8f92ea6bf394640" @@ -5762,7 +5762,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3120, + "y": 3180, "wires": [] }, { @@ -5784,7 +5784,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3160, + "y": 3220, "wires": [ [ "06397bb46b3bb541" @@ -5805,7 +5805,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3160, + "y": 3220, "wires": [] }, { @@ -5827,7 +5827,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3200, + "y": 3260, "wires": [ [ "687dcdc1ede11700" @@ -5848,7 +5848,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3200, + "y": 3260, "wires": [] }, { @@ -5870,7 +5870,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3240, + "y": 3300, "wires": [ [ "e220740c0d38ccb0" @@ -5891,7 +5891,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3240, + "y": 3300, "wires": [] }, { @@ -5913,7 +5913,7 @@ "className": "", "topicType": "msg", "x": 390, - "y": 3280, + "y": 3340, "wires": [ [ "79d7e5a705ab813a" @@ -5934,7 +5934,7 @@ "layout": "row-spread", "className": "", "x": 730, - "y": 3280, + "y": 3340, "wires": [] }, { @@ -5956,7 +5956,7 @@ "className": "", "topicType": "msg", "x": 400, - "y": 3320, + "y": 3380, "wires": [ [ "12d20f2274bcc511" @@ -5977,7 +5977,7 @@ "layout": "row-spread", "className": "", "x": 740, - "y": 3320, + "y": 3380, "wires": [] }, { @@ -6038,7 +6038,7 @@ "step": "90", "className": "", "x": 410, - "y": 2880, + "y": 2940, "wires": [ [ "3019576de193d9d6" @@ -6059,7 +6059,7 @@ "layout": "row-spread", "className": "", "x": 750, - "y": 2880, + "y": 2940, "wires": [] }, { @@ -6074,7 +6074,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2060, + "y": 2120, "wires": [ [ "dfdebe10dbf0e198" @@ -6093,7 +6093,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2060, + "y": 2120, "wires": [ [] ] @@ -6110,7 +6110,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2180, + "y": 2240, "wires": [ [ "9a56c087d941f1da" @@ -6129,7 +6129,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2180, + "y": 2240, "wires": [ [] ] @@ -6146,7 +6146,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2220, + "y": 2280, "wires": [ [ "0dfc86d90258f9bb" @@ -6165,7 +6165,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2220, + "y": 2280, "wires": [ [] ] @@ -6182,7 +6182,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2260, + "y": 2320, "wires": [ [ "1361134e9847f003" @@ -6201,7 +6201,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2260, + "y": 2320, "wires": [ [] ] @@ -6218,7 +6218,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2100, + "y": 2160, "wires": [ [ "b03e8b51187e88eb" @@ -6237,7 +6237,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2100, + "y": 2160, "wires": [ [] ] @@ -6254,7 +6254,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2140, + "y": 2200, "wires": [ [ "543e1690693acbeb" @@ -6273,7 +6273,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2140, + "y": 2200, "wires": [ [] ] @@ -6290,7 +6290,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2300, + "y": 2360, "wires": [ [ "c6642c7470d3820c" @@ -6309,7 +6309,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2420, + "y": 2480, "wires": [ [ "721b9680a3fa460e" @@ -6328,7 +6328,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2460, + "y": 2520, "wires": [ [ "1610895f430b9aca" @@ -6347,7 +6347,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2500, + "y": 2560, "wires": [ [ "277037c4716d85bf" @@ -6366,7 +6366,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2340, + "y": 2400, "wires": [ [ "6aae9d4fddf08cc0" @@ -6385,7 +6385,7 @@ "finalize": "", "libs": [], "x": 290, - "y": 2380, + "y": 2440, "wires": [ [ "10687d331a732790" @@ -6404,7 +6404,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2300, + "y": 2360, "wires": [ [] ] @@ -6421,7 +6421,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2420, + "y": 2480, "wires": [ [] ] @@ -6438,7 +6438,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2460, + "y": 2520, "wires": [ [] ] @@ -6455,7 +6455,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2500, + "y": 2560, "wires": [ [] ] @@ -6472,7 +6472,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2340, + "y": 2400, "wires": [ [] ] @@ -6489,7 +6489,7 @@ "finalize": "", "libs": [], "x": 630, - "y": 2380, + "y": 2440, "wires": [ [] ] @@ -6506,7 +6506,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2600, + "y": 2660, "wires": [ [ "2522f888dc58972f" @@ -6525,7 +6525,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2600, + "y": 2660, "wires": [ [] ] @@ -6542,7 +6542,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2640, + "y": 2700, "wires": [ [ "30e8df3d616512d8" @@ -6561,7 +6561,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2640, + "y": 2700, "wires": [ [] ] @@ -6578,7 +6578,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2760, + "y": 2820, "wires": [ [ "d855d926df89d65b" @@ -6597,7 +6597,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2760, + "y": 2820, "wires": [ [] ] @@ -6614,7 +6614,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2800, + "y": 2860, "wires": [ [ "7617517dc8ba2859" @@ -6633,7 +6633,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2800, + "y": 2860, "wires": [ [] ] @@ -6650,7 +6650,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2840, + "y": 2900, "wires": [ [ "cbaa23c34e10fae1" @@ -6669,7 +6669,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2840, + "y": 2900, "wires": [ [] ] @@ -6686,7 +6686,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2880, + "y": 2940, "wires": [ [ "f455fb39039617ae" @@ -6705,7 +6705,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2880, + "y": 2940, "wires": [ [] ] @@ -6722,7 +6722,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3000, + "y": 3060, "wires": [ [ "e89f61dbe6a6cffe" @@ -6741,7 +6741,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3000, + "y": 3060, "wires": [ [] ] @@ -6758,7 +6758,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3320, + "y": 3380, "wires": [ [ "eef25405472acfee" @@ -6777,7 +6777,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3320, + "y": 3380, "wires": [ [] ] @@ -6794,7 +6794,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3040, + "y": 3100, "wires": [ [ "70014da0b6ab6698" @@ -6813,7 +6813,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3040, + "y": 3100, "wires": [ [] ] @@ -6830,7 +6830,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3080, + "y": 3140, "wires": [ [ "2544963852c6881a" @@ -6849,7 +6849,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3080, + "y": 3140, "wires": [ [] ] @@ -6866,7 +6866,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3120, + "y": 3180, "wires": [ [ "a1394401246eb735" @@ -6885,7 +6885,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3120, + "y": 3180, "wires": [ [] ] @@ -6902,7 +6902,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3160, + "y": 3220, "wires": [ [ "f15ca4518b5f223e" @@ -6921,7 +6921,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3160, + "y": 3220, "wires": [ [] ] @@ -6938,7 +6938,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3200, + "y": 3260, "wires": [ [ "49900bb9047dd965" @@ -6957,7 +6957,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3200, + "y": 3260, "wires": [ [] ] @@ -6974,7 +6974,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3240, + "y": 3300, "wires": [ [ "5a90224dc998b417" @@ -6993,7 +6993,7 @@ "finalize": "", "libs": [], "x": 270, - "y": 3280, + "y": 3340, "wires": [ [ "d2364ab09627fe94" @@ -7012,7 +7012,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3240, + "y": 3300, "wires": [ [] ] @@ -7029,7 +7029,7 @@ "finalize": "", "libs": [], "x": 590, - "y": 3280, + "y": 3340, "wires": [ [] ] @@ -7471,7 +7471,7 @@ "960912e90ba5b5bc" ], "x": 155, - "y": 2540, + "y": 2600, "wires": [ [ "43fe948b3e7234e2", @@ -7494,7 +7494,7 @@ "50eeb3e362f9027f" ], "x": 135, - "y": 3000, + "y": 3060, "wires": [ [ "77bb7dc529d63a7e", @@ -7649,7 +7649,7 @@ "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_contrast?contrast=\" + str(msg['payload']))", "outputs": 1, "x": 660, - "y": 2720, + "y": 2780, "wires": [ [] ] @@ -7662,7 +7662,7 @@ "func": "from OpenScan import camera\n\ncamera(\"/v1/camera/picam2_saturation?saturation=\" + str(msg['payload']))", "outputs": 1, "x": 660, - "y": 2680, + "y": 2740, "wires": [ [] ] @@ -7687,7 +7687,7 @@ "step": "0.02", "className": "", "x": 440, - "y": 2560, + "y": 2620, "wires": [ [ "e612073aded01a8f" @@ -7708,7 +7708,7 @@ "layout": "row-spread", "className": "", "x": 760, - "y": 2560, + "y": 2620, "wires": [] }, { @@ -7723,7 +7723,7 @@ "finalize": "", "libs": [], "x": 280, - "y": 2560, + "y": 2620, "wires": [ [ "81bd4381cd029958" @@ -7742,7 +7742,7 @@ "finalize": "", "libs": [], "x": 620, - "y": 2560, + "y": 2620, "wires": [ [] ] @@ -8098,7 +8098,7 @@ "label": "", "tooltip": "", "group": "7a3279eea439bcdd", - "order": 12, + "order": 14, "width": 3, "height": 1, "passthru": false, @@ -8117,23 +8117,6 @@ ] ] }, - { - "id": "69516440e3997111", - "type": "ui_text", - "z": "e43a27722b508115", - "group": "7a3279eea439bcdd", - "order": 11, - "width": 3, - "height": 1, - "name": "rotor_endstop_angle_label", - "label": "Endstop angle", - "format": "", - "layout": "row-left", - "className": "", - "x": 820, - "y": 2020, - "wires": [] - }, { "id": "85ad07b8f973bbe2", "type": "function", @@ -8174,7 +8157,7 @@ "id": "253feafa5a2f8b1d", "type": "ui_switch", "z": "e43a27722b508115", - "name": "rotor_enable_endstop", + "name": "rotor_endstop_enable", "label": "", "tooltip": "", "group": "7a3279eea439bcdd", @@ -8210,7 +8193,7 @@ "type": "function", "z": "e43a27722b508115", "name": "loadB", - "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", + "func": "var file = 'rotor_endstop_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nvar data = fs.readFileSync(filepath+file, 'utf8');\nif(data === '1' || data === 'True' || data === 'true'){\n data = true;\n}\nelse{\n data = false;\n}\nmsg.payload = data;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", @@ -8229,7 +8212,7 @@ "type": "function", "z": "e43a27722b508115", "name": "write", - "func": "var file = 'rotor_enable_endstop'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "func": "var file = 'rotor_endstop_enable'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", "outputs": 1, "noerr": 0, "initialize": "", @@ -8249,7 +8232,7 @@ "order": 9, "width": 3, "height": 1, - "name": "rotor_enable_endstop_label", + "name": "rotor_endstop_enable_label", "label": "Enable Endstop", "format": "", "layout": "row-left", @@ -8273,8 +8256,10 @@ "y": 1980, "wires": [ [ + "f036424d79645761", + "c7a41014cbbff864", "69516440e3997111", - "f036424d79645761" + "e9a307a44d9f29f1" ] ] }, @@ -8930,6 +8915,110 @@ ] ] }, + { + "id": "c7a41014cbbff864", + "type": "function", + "z": "e43a27722b508115", + "name": "loadI", + "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst data = fs.readFileSync(filepath+file, 'utf8');\nmsg.payload = parseInt(data);\nreturn msg", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 290, + "y": 2060, + "wires": [ + [ + "5712cdb7d571f18c" + ] + ] + }, + { + "id": "5712cdb7d571f18c", + "type": "ui_switch", + "z": "e43a27722b508115", + "name": "rotor_endstop_pushed", + "label": "", + "tooltip": "", + "group": "7a3279eea439bcdd", + "order": 12, + "width": 3, + "height": 1, + "passthru": true, + "decouple": "false", + "topic": "topic", + "topicType": "msg", + "style": "", + "onvalue": "true", + "onvalueType": "bool", + "onicon": "", + "oncolor": "", + "offvalue": "false", + "offvalueType": "bool", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 460, + "y": 2060, + "wires": [ + [ + "168334311f605afd" + ] + ] + }, + { + "id": "168334311f605afd", + "type": "function", + "z": "e43a27722b508115", + "name": "write", + "func": "var file = 'rotor_endstop_pushed'\nlet fs = global.get('fs');\nvar filepath = '/home/pi/OpenScan/settings/';\nconst content = String(msg.payload)\nfs.writeFile(filepath + file, content, err => {\n if (err) {\n return\n }\n });", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 630, + "y": 2060, + "wires": [ + [] + ] + }, + { + "id": "69516440e3997111", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 13, + "width": 3, + "height": 1, + "name": "rotor_endstop_angle_label", + "label": "Endstop angle", + "format": "", + "layout": "row-left", + "className": "", + "x": 820, + "y": 2020, + "wires": [] + }, + { + "id": "e9a307a44d9f29f1", + "type": "ui_text", + "z": "e43a27722b508115", + "group": "7a3279eea439bcdd", + "order": 11, + "width": 3, + "height": 1, + "name": "rotor_endstop_pushed_label", + "label": "Endstop reverse", + "format": "", + "layout": "row-left", + "className": "", + "x": 820, + "y": 2060, + "wires": [] + }, { "id": "4c7fa5b5b27b83a5", "type": "python3-function", From faec8ab05aa3adac5120f971efeebe3e581f9789 Mon Sep 17 00:00:00 2001 From: Unus-Multorum Date: Sun, 13 Oct 2024 21:10:29 +0200 Subject: [PATCH 37/38] Update update.json Added new file sizes --- update/2024-1o/update.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/update/2024-1o/update.json b/update/2024-1o/update.json index b1858ad..9d0651c 100644 --- a/update/2024-1o/update.json +++ b/update/2024-1o/update.json @@ -3,7 +3,7 @@ "1": { "src": "stable/OpenScan.py", "dst": "/usr/lib/python3/dist-packages/OpenScan.py", - "filesize": 10396 + "filesize": 10524 }, "2": { "src": "stable/OpenScanStatistics.py", @@ -28,7 +28,7 @@ "6": { "src": "stable/flows.json", "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", - "filesize": 342710 + "filesize": 355845 }, "7": { "src": "stable/settings.js", From bc68c573b7df3c43ab0b8446fa7f3431fa8cf722 Mon Sep 17 00:00:00 2001 From: Unus-Multorum Date: Sun, 13 Oct 2024 21:20:01 +0200 Subject: [PATCH 38/38] Last update for endstops Params now are the same as the new function --- update/2024-1o/meanwhile/fla.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/update/2024-1o/meanwhile/fla.py b/update/2024-1o/meanwhile/fla.py index a445a79..c66cc4b 100644 --- a/update/2024-1o/meanwhile/fla.py +++ b/update/2024-1o/meanwhile/fla.py @@ -522,8 +522,7 @@ class MotorRun(Resource): @motor_ns.doc(params={ 'motor': 'Motor name (rotor, tt, extra)', 'angle': 'Angle to rotate (integer)', - 'ES_enable': 'Enable endstop (optional, boolean)', - 'ES_start_state': 'Endstop start state (optional, boolean)' + 'endstop': 'Enable endstop (optional, boolean)' }) @motor_ns.response(400, 'Bad Request') def get(self):