├── .github └── FUNDING.yml ├── 2024-10 ReCap.md ├── LICENSE ├── README.md ├── mkdocs.yml ├── software_dev_outline.md └── update ├── beta ├── Arducam.py ├── OpenScan.py ├── config.txt ├── fla.py ├── flows.json └── settings.js ├── betaArdu ├── Arducam.py ├── OpenScan.py ├── config.txt ├── fla.py ├── flows.json └── settings.js ├── files ├── logo.jpg └── logo2.jpg ├── main ├── Arducam.py ├── OpenScan.py ├── config.txt ├── fla.py ├── flows.json └── settings.js ├── mini ├── Arducam.py ├── OpenScan.py ├── config.txt ├── fla.py ├── flows.json └── settings.js └── update.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: OpenScan 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /2024-10 ReCap.md: -------------------------------------------------------------------------------- 1 | # OpenScan - Bringing 3D scanning to the masses - what was, what is and what can be? 2 | 3 | ***In the following sections, the OpenScan project is outlined. It is important to understand the background and the bumpy ride that brought us here in order to lay a foundation for future developments*** 4 | 5 | ___ 6 | ___ 7 | 8 | ## Background 9 | Back in 2017, I was looking for an accessible 3D scanning solution and quickly realised, that there was none. All existing solutions either came with a hefty price tag or were not able to create decent 3D scans. 10 | 11 | I stumbled upon photogrammetry as a very powerful tool and played around with it as it only requires a camera. Refining the lighting and capture settings, I was able to get very decent scans. When I was able to copy both my mailbox as well as my "security" home key, I realized that photogrammetry can yield great results. 12 | 13 | Being a lazy person, I started to automate the process using an *Arduino Nano*, which was tilting and rotating the object. Adding a ringlight to the camera for better lighting and most of the capture process was automated. It is important to state, that I had NO background in electronics, programming or community management. Every of those skills (and many others) needed to evolve during the development process. 14 | 15 | **Nothing would have been possible without the immense support and contribution from the very open-minded community. Having so many useful resources publicly available has always been a major cornerstone of the project. For this and many other reasons, being open-source is a core value of the project. Becoming able to contribute to the open-source movement is such an amazing honor!** 16 | 17 | In 2019, I changed the plattform in favor of the *Raspberry Pi*. This step came with new challenges but also a lot of new options to increase the scanners capabilities. My main goal was to **automate and simplify as much of the photogrammetry capture process as much as possible - maybe even create a one-click-3d-scanning-solution.** 18 | 19 | In 2021, the optional *OpenScan Cloud* was launched. This lowered the entry barrier even further, as many users either did not want or could not do the processing locally (as this always requires some software knowledge as well as a capable computer). Thanks to the support through Patreon & BuyMeACoffee, the OpenScan Cloud maintains its state till today. 20 | 21 | Early 2022 Arducam released their *16mp IMX519* camera with focus control. This allowed an even finer control of the scanning process. Adding *focus stacking* to the process increased the quality of the resulting 3D models even further. To this point, the OpenScan project grew on various channels and reached people in over 70 countries. 22 | 23 | Unfortunately, the evolution of the project created an almost unmanageable pile of documents/codes/files across various platforms and github repositories. There have been several attempts of restructuring and reorganizing the whole structure, but by this point, the sheer amount of data has been totally overwhelming for me. Producing and distributing scanner kits as a business brought its own amount of challenges (customer support, accounting, production, delivery bottlenecks, legal issues ...). 24 | By this time, i felt almost incapable of further developing the project and almost abandonned it completely. 25 | 26 | **Fortunately, there have been several idealistic and very eager community members (especially on the OpenScan Discord), which not only welcomed new OpenScan users and patiently helped with all the existing and known issues of the system. But they also started several community developments, which solved many of the existing hardware and software issues.** 27 | 28 | ___ 29 | ___ 30 | 31 | ## Current State of the project 32 | 33 | | great | not so great | 34 | | -- | -- | 35 | |- modularity
- wide user base across all continents
- used for archeology, dental, research, creation of gaming assets, reverse engineering, miniatures
- the only open-source and low-cost 3D scanning solution
- high quality 3D models
- a lot of potential
- great understanding of the underlying principles and available knowledge |- disconnect between official and community
- many (known) issues in the offical versions --> bad user experience
- relatively high effort needed to get started
- scattered and outdated documentation
- no organizational structure
- no structure for contribution and appreciation thereof | 36 | 37 | ___ 38 | ### Hardware 39 | 40 | | | OpenScan Mini V1 | OpenScan Classic | OpenScan Mini V2 | OpenScan Midi | 41 | | -- | -- | -- | -- | -- | 42 | | **state** | official | official | community | community | 43 | | **more details** | [github](https://openscan-org.github.io/OpenScan-Doc/hardware/OpenScanMini/) | [github](https://openscan-org.github.io/OpenScan-Doc/hardware/OpenScanClassic/)| [github](https://github.com/OpenScan-org/OpenScan-Design/tree/main?tab=readme-ov-file#openscan-mini-v2) | [github](https://openscan-org.github.io/OpenScan-Doc/hardware/OpenScanClassic/) | 44 | 45 | ___ 46 | ### Firmware/Software 47 | 48 | | | Official Firmware | "Patreon Beta" | OpenScan Meanwhile | OpenScan Composer 49 | | -- | -- | -- | -- | -- | 50 | | **state** | official | partly official | community | community | 51 | | **more details** | [github](https://openscan-org.github.io/OpenScan-Doc/firmware/setup/) | [patreon (free)](https://www.patreon.com/posts/beta-firmware-2-86937106) | [github](https://github.com/stealthizer/OpenScan2/tree/2024-1o)
[roadmap](https://miro.com/app/board/uXjVNrJGlbQ=/) | [OpenScanComposer.com](https://www.openscancomposer.com/)| 52 | 53 | ___ 54 | ### Community 55 | 56 | | Channel | Focus | 57 | | -- | --| 58 | | [Discord](https://discord.gg/gpaKWPpWtG) | - community support
- coordination of development | 59 | | [OpenScan.eu](http://openscan.eu) | - official website
- entry point for most new users | 60 | | [reddit/r/OpenScan](https://www.reddit.com/r/OpenScan/) | - community support
- show and tell| 61 | 62 | ___ 63 | ### Electronics 64 | 65 | There are two core components: 66 | * The **pi shield** directly connects on top of the Raspberry Pi and is used to interfer with the motors, camera and lighting. 67 | * The **ringlight module** for standard pi camera form factor cameras allows for optimal illumination. 68 | 69 | It is noteworthy, that these two PCBs are not strictly necessary to build a 3D scanner, but greatly simplifies the overall process. 70 | 71 | ___ 72 | #### Pi Shield 73 | 74 | | | Green Shield (pre-soldered) | Green Shield (solder yourself) | Black Shield | 4 Axis Shield | 75 | | -- | -- | -- | -- | -- | 76 | | **state** | official | official | community | unpublished | 77 | | **more details** | link | link | link | link | 78 | 79 | ___ 80 | #### Ringlight 81 | 82 | ___ 83 | ### OpenScanCloud 84 | 85 | The [OpenScanCloud](https://github.com/OpenScan-org/OpenScanCloud) is a free and donation-based online photogrammetry processing pipeline with increasing popularity. Its simplicity (one-click) allows users to avoid the need for local processing power and knowledge of a dedicated software. 86 | **It is solely financed through Patreon donations and there will never be any kind of monetization crippling its functionality!** 87 | 88 | - automated focus stacking (though it is not documented at all), when using the openscan firmware 89 | - ~0.5TB of Data per month 90 | - minor issues with data transmission 91 | - hard limitation to 2GB max filesize 92 | - manual access token creation and user management 93 | - missing documentation 94 | - rudimentary windows uploader and python script 95 | 96 | ___ 97 | ### OpenScanBenchy 98 | 99 | An approach to create a recognizable benchmark for small object 3d scanners. Details on [github](https://github.com/OpenScanEu/OpenScanBenchy/tree/main) 100 | 101 | ___ 102 | ___ 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenScan2 - 3D Scanner 2 | ## Related and more specific repositories 3 | 4 | If you want to take part in the development of a specific part of the OpenScan system, feel free to join: 5 | 6 | * [OpenScanCloud - Web API for photogrammetry processing of image files](https://github.com/OpenScanEu/OpenScanCloud) 7 | * [OpenScan-Design - 3D printable files and other design approaches](https://github.com/OpenScanEu/OpenScan-Design) 8 | * [OpenScan-PCB - A place to discuss and improve the PCB designs](https://github.com/OpenScanEu/OpenScan-PCB) 9 | * [OpenScan-ML - Development of new tools using Machine Learning](https://github.com/OpenScanEu/OpenScan-ML) 10 | 11 | ## Contribution and contributors 12 | 13 | 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) 14 | 15 | Thank you! 16 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: OpenScan 2 | plugins: 3 | - autolinks 4 | - search: 5 | lang: 6 | - en 7 | theme: 8 | name: material 9 | language: en 10 | palette: 11 | primary: teal 12 | accent: indigo 13 | features: 14 | - content.code.annotate 15 | - search.suggest 16 | - search.highlight 17 | markdown_extensions: 18 | - pymdownx.highlight: 19 | anchor_linenums: true 20 | - pymdownx.inlinehilite 21 | - pymdownx.snippets 22 | - pymdownx.emoji: 23 | emoji_index: !!python/name:materialx.emoji.twemoji 24 | emoji_generator: !!python/name:materialx.emoji.to_svg 25 | - attr_list 26 | - md_in_html 27 | - admonition 28 | - pymdownx.details 29 | - pymdownx.superfences 30 | - pymdownx.tabbed: 31 | alternate_style: true 32 | - pymdownx.critic 33 | - pymdownx.caret 34 | - pymdownx.keys 35 | - pymdownx.mark 36 | - pymdownx.tilde 37 | - def_list 38 | - pymdownx.tasklist: 39 | custom_checkbox: true 40 | 41 | nav: 42 | - Introduction: "index.md" 43 | - Builds: 44 | - Classic: "builds/OpenScanClassic.md" 45 | - Mini: "builds/OpenScanMini.md" 46 | - PCB: "builds/PCBs.md" 47 | - Firmware: 48 | - Setup: "firmware/setup.md" 49 | - Usage: "firmware/usage.md" 50 | - Photogrammetry: 51 | - Basics: "photogrammetry/basics.md" 52 | - Software: "photogrammetry/software.md" 53 | - Changelog: "changelog.md" 54 | -------------------------------------------------------------------------------- /software_dev_outline.md: -------------------------------------------------------------------------------- 1 | Based on [OpenScan3](https://github.com/OpenScan-org/OpenScan3) 2 | 3 | ## 1. Core Infrastructure & Setup 4 | 5 | - [ ] Update initial setup procedure 6 | - [x] Basic FastAPI application structure setup (already done in app-main.py) 7 | - [ ] Configure settings for different hardware setups (greenshield, blackshield, grblHAL) 8 | - [ ] Set up error handling and logging system 9 | - [ ] Implement Network security 10 | - [ ] Use virtual environment 11 | - [ ] Integrate ramdisk for faster temporary file handling 12 | 13 | ### 1.1. Network & Connectivity 14 | - [ ] Add hotspot mode 15 | - [ ] Add wifi configuration & testing 16 | - [ ] Add test for internet connectivity 17 | 18 | ## 2. Hardware Control Components 19 | 20 | ### 2.1. Camera System 21 | 22 | - [ ] Review and update camera controllers (gphoto2, v4l2, picamera2) 23 | - [ ] Implement unified camera interface 24 | - [ ] Implement camera settings management 25 | - [ ] Integrate Focus control system: 26 | - [ ] Integrate software controlled focus 27 | - [ ] Add mechanic focus through third motor 28 | 29 | ### 2.2. Motor Control System 30 | 31 | - [ ] Migrate motor controllers from old system (directly through GPIO) 32 | - [ ] Add Motor controller through GRBLhal 33 | - [ ] Integrate optional endstops 34 | - [ ] Implement motor movement coordination 35 | - [ ] Add tests for users 36 | 37 | ### 2.3. Light Control system 38 | 39 | - [ ] Migrate Light controller (directly through GPIO) 40 | - [ ] Add Light controller through GRBLhal 41 | - [ ] Add tests for users 42 | 43 | ### 2.4. Fan Control System 44 | - [ ] Add fan controller (directly through GPIO) 45 | - [ ] Add Fan controller through GRBLhal 46 | - [ ] Add Temperature dependent fan control 47 | 48 | ### 2.5. Other Peripherals ? 49 | 50 | ## 3. Scanning Logic 51 | 52 | ### 3.1 Smart Pre-Scan systems (nice to have/optional) 53 | 54 | - [ ] Add auto-exposure detection based on histogram 55 | - [ ] Add auto-crop detection routine 56 | - [ ] Add auto-depth detection 57 | - [ ] Add Evaluation of object preparation (feature detection) 58 | 59 | ### 3.2. Core Scanning System 60 | 61 | - [ ] Add scan templates/presets 62 | - [ ] Migrate scanning process controller 63 | - [ ] Implement proper scan state management 64 | - [ ] Add scan progress tracking 65 | - [ ] Add resume from failure point 66 | - [ ] Implement pause/resume functionality 67 | 68 | ### 3.3. Path Generation 69 | 70 | - [ ] Migrate different scanning patterns (Grid, Fibonacci, Spiral, Archimedes) 71 | - [ ] Implement path optimization 72 | - [ ] Add path visualization support 73 | 74 | ### 4. Scan Project Handling 75 | 76 | - [ ] Implement project creation and management 77 | - [ ] Implement proper file structure for projects 78 | - [ ] Add External drive for saving 79 | - [ ] Add network drive for saving 80 | - [ ] Add project metadata handling 81 | - [ ] Add download project 82 | - [ ] Add delete project 83 | - [ ] Add delete all projects 84 | - [ ] Add merge projects 85 | - [ ] Add scan meta data (positions, focus, resolution, timestamps) 86 | 87 | ## 5. Processing Integration 88 | 89 | ### 5.1. OpenScan Cloud 90 | 91 | - [ ] Migrate cloud upload functionality 92 | - [ ] Implement secure authentication 93 | - [ ] Add progress tracking for uploads 94 | - [ ] Implement download functionality 95 | 96 | ### 5.2. create Project files for other programs 97 | - [ ] Metashape 98 | - [ ] Reality Capture 99 | - [ ] 3DF Zephyr 100 | - [ ] Meshroom 101 | 102 | ## 6. System Services 103 | 104 | - [ ] Migrate system status monitoring 105 | - [ ] Implement proper shutdown/reboot handlers 106 | - [ ] Add system health checks 107 | - [ ] Add system statistics 108 | - [ ] Add Diskspace monitoring 109 | - [ ] Add Diskspace warnings 110 | - [ ] Implement update service 111 | - [ ] Implement update versioning 112 | - [ ] Add Change Log 113 | - [ ] Add Server message service 114 | - [ ] Add Samba client 115 | - [ ] Add SSH on/off 116 | 117 | 118 | ## 7. Testing ?? 119 | - [ ] Set up unit testing infrastructure 120 | - [ ] Add integration tests 121 | - [ ] Implement hardware simulation for testing 122 | - [ ] Add CI/CD pipeline 123 | 124 | 125 | ## 8. Documentation 126 | 127 | - [ ] API documentation 128 | - [ ] System architecture documentation 129 | - [ ] Hardware setup documentation 130 | - [ ] User guide 131 | 132 | 133 | -------------------------------------------------------------------------------- /update/beta/Arducam.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | 4 | try: 5 | import v4l2 6 | except Exception as e: 7 | print(e) 8 | print("Try to install v4l2-fix") 9 | try: 10 | from pip import main as pipmain 11 | except ImportError: 12 | from pip._internal import main as pipmain 13 | pipmain(['install', 'v4l2-fix']) 14 | print("\nTry to run the focus program again.") 15 | exit(0) 16 | 17 | import fcntl 18 | import errno 19 | 20 | # # Type 21 | # v4l2.V4L2_CTRL_TYPE_INTEGER 22 | # v4l2.V4L2_CTRL_TYPE_BOOLEAN 23 | # v4l2.V4L2_CTRL_TYPE_MENU 24 | # v4l2.V4L2_CTRL_TYPE_BUTTON 25 | # v4l2.V4L2_CTRL_TYPE_INTEGER64 26 | # v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 27 | # # Flags 28 | # v4l2.V4L2_CTRL_FLAG_DISABLED 29 | # v4l2.V4L2_CTRL_FLAG_GRABBED 30 | # v4l2.V4L2_CTRL_FLAG_READ_ONLY 31 | # v4l2.V4L2_CTRL_FLAG_UPDATE 32 | # v4l2.V4L2_CTRL_FLAG_INACTIVE 33 | # v4l2.V4L2_CTRL_FLAG_SLIDER 34 | 35 | def assert_valid_queryctrl(queryctrl): 36 | return queryctrl.type & ( 37 | v4l2.V4L2_CTRL_TYPE_INTEGER 38 | | v4l2.V4L2_CTRL_TYPE_BOOLEAN 39 | | v4l2.V4L2_CTRL_TYPE_MENU 40 | | v4l2.V4L2_CTRL_TYPE_BUTTON 41 | | v4l2.V4L2_CTRL_TYPE_INTEGER64 42 | | v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 43 | | 7 44 | | 8 45 | | 9 46 | ) and queryctrl.flags & ( 47 | v4l2.V4L2_CTRL_FLAG_DISABLED 48 | | v4l2.V4L2_CTRL_FLAG_GRABBED 49 | | v4l2.V4L2_CTRL_FLAG_READ_ONLY 50 | | v4l2.V4L2_CTRL_FLAG_UPDATE 51 | | v4l2.V4L2_CTRL_FLAG_INACTIVE 52 | | v4l2.V4L2_CTRL_FLAG_SLIDER 53 | ) 54 | 55 | def get_device_controls_menu(fd, queryctrl): 56 | querymenu = v4l2.v4l2_querymenu(queryctrl.id, queryctrl.minimum) 57 | while querymenu.index <= queryctrl.maximum: 58 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYMENU, querymenu) 59 | yield querymenu 60 | querymenu.index += 1 61 | 62 | def get_device_controls_by_class(fd, control_class): 63 | # enumeration by control class 64 | queryctrl = v4l2.v4l2_queryctrl(control_class | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 65 | while True: 66 | try: 67 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 68 | except IOError as e: 69 | assert e.errno == errno.EINVAL 70 | break 71 | if v4l2.V4L2_CTRL_ID2CLASS(queryctrl.id) != control_class: 72 | break 73 | yield queryctrl 74 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 75 | 76 | def getdict(struct): 77 | val = dict((field, getattr(struct, field)) for field, _ in struct._fields_) 78 | val.pop("reserved") 79 | return val 80 | 81 | def get_device_controls(fd): 82 | # original enumeration method 83 | queryctrl = v4l2.v4l2_queryctrl(v4l2.V4L2_CID_BASE) 84 | while queryctrl.id < v4l2.V4L2_CID_LASTP1: 85 | try: 86 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 87 | print(queryctrl.name) 88 | except IOError as e: 89 | # this predefined control is not supported by this device 90 | assert e.errno == errno.EINVAL 91 | queryctrl.id += 1 92 | continue 93 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id + 1) 94 | 95 | def get_ctrls(vd): 96 | ctrls = [] 97 | # enumeration by control class 98 | for class_ in (v4l2.V4L2_CTRL_CLASS_USER, v4l2.V4L2_CTRL_CLASS_MPEG, v4l2.V4L2_CTRL_CLASS_CAMERA): 99 | for queryctrl in get_device_controls_by_class(vd, class_): 100 | ctrl = getdict(queryctrl) 101 | if queryctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: 102 | ctrl["menu"] = [] 103 | for querymenu in get_device_controls_menu(vd, queryctrl): 104 | # print(querymenu.name) 105 | ctrl["menu"].append(querymenu.name) 106 | 107 | if queryctrl.type == 9: 108 | ctrl["menu"] = [] 109 | for querymenu in get_device_controls_menu(vd, queryctrl): 110 | ctrl["menu"].append(querymenu.index) 111 | ctrls.append(ctrl) 112 | return ctrls 113 | 114 | def set_ctrl(vd, id, value): 115 | ctrl = v4l2.v4l2_control() 116 | ctrl.id = id 117 | ctrl.value = value 118 | try: 119 | fcntl.ioctl(vd, v4l2.VIDIOC_S_CTRL, ctrl) 120 | except IOError as e: 121 | print(e) 122 | 123 | def get_ctrl(vd, id): 124 | ctrl = v4l2.v4l2_control() 125 | ctrl.id = id 126 | try: 127 | fcntl.ioctl(vd, v4l2.VIDIOC_G_CTRL, ctrl) 128 | except IOError as e: 129 | print(e) 130 | return None 131 | return ctrl.value 132 | 133 | 134 | class Focuser: 135 | FOCUS_ID = 0x009a090a 136 | dev = None 137 | 138 | def __init__(self, dev=0): 139 | self.focus_value = 0 140 | self.dev = dev 141 | 142 | if type(dev) == int or (type(dev) == str and dev.isnumeric()): 143 | self.dev = "/dev/video{}".format(dev) 144 | 145 | self.fd = open(self.dev, 'r') 146 | self.ctrls = get_ctrls(self.fd) 147 | self.hasFocus = False 148 | for ctrl in self.ctrls: 149 | if ctrl['id'] == Focuser.FOCUS_ID: 150 | self.hasFocus = True 151 | self.opts[Focuser.OPT_FOCUS]["MIN_VALUE"] = ctrl['minimum'] 152 | self.opts[Focuser.OPT_FOCUS]["MAX_VALUE"] = ctrl['maximum'] 153 | self.opts[Focuser.OPT_FOCUS]["DEF_VALUE"] = ctrl['default'] 154 | self.focus_value = get_ctrl(self.fd, Focuser.FOCUS_ID) 155 | 156 | if not self.hasFocus: 157 | raise RuntimeError("Device {} has no focus_absolute control.".format(self.dev)) 158 | 159 | def read(self): 160 | return self.focus_value 161 | 162 | def write(self, value): 163 | self.focus_value = value 164 | # os.system("v4l2-ctl -d {} -c focus_absolute={}".format(self.dev, value)) 165 | set_ctrl(self.fd, Focuser.FOCUS_ID, value) 166 | 167 | OPT_BASE = 0x1000 168 | OPT_FOCUS = OPT_BASE | 0x01 169 | OPT_ZOOM = OPT_BASE | 0x02 170 | OPT_MOTOR_X = OPT_BASE | 0x03 171 | OPT_MOTOR_Y = OPT_BASE | 0x04 172 | OPT_IRCUT = OPT_BASE | 0x05 173 | opts = { 174 | OPT_FOCUS : { 175 | "MIN_VALUE": 0, 176 | "MAX_VALUE": 1000, 177 | "DEF_VALUE": 0, 178 | }, 179 | } 180 | def reset(self,opt,flag = 1): 181 | info = self.opts[opt] 182 | if info == None or info["DEF_VALUE"] == None: 183 | return 184 | self.set(opt,info["DEF_VALUE"]) 185 | 186 | def get(self,opt,flag = 0): 187 | info = self.opts[opt] 188 | return self.read() 189 | 190 | def set(self,opt,value,flag = 1): 191 | info = self.opts[opt] 192 | if value > info["MAX_VALUE"]: 193 | value = info["MAX_VALUE"] 194 | elif value < info["MIN_VALUE"]: 195 | value = info["MIN_VALUE"] 196 | self.write(value) 197 | print("write: {}".format(value)) 198 | 199 | def __del__(self): 200 | self.fd.close() 201 | 202 | pass 203 | -------------------------------------------------------------------------------- /update/beta/OpenScan.py: -------------------------------------------------------------------------------- 1 | basepath = '/home/pi/OpenScan/' 2 | from os.path import isfile 3 | 4 | def load_bool(name): 5 | filename = basepath+'settings/'+name 6 | if not isfile(filename): 7 | return 8 | with open(filename, 'r') as file: 9 | value = file.read().replace('\n','') 10 | if value == '1' or value == 'True' or value =='true': 11 | value = True 12 | else: 13 | value = False 14 | return value 15 | 16 | def load_str(name): 17 | filename = basepath+'settings/'+name 18 | if not isfile(filename): 19 | return 20 | with open(filename, 'r') as file: 21 | value = file.read().replace('\n','') 22 | return value 23 | 24 | def load_int(name): 25 | filename = basepath+'settings/'+name 26 | if not isfile(filename): 27 | return 28 | with open(filename, 'r') as file: 29 | value = int(file.read().replace('\n','')) 30 | return value 31 | 32 | def load_float(name): 33 | filename = basepath+'settings/'+name 34 | if not isfile(filename): 35 | return 36 | with open(filename, 'r') as file: 37 | value = float(file.read().replace('\n','')) 38 | return value 39 | 40 | def save(name, value): 41 | filename = basepath+'settings/'+name 42 | with open(filename, 'w+') as file: 43 | file.write(str(value)) 44 | return 45 | 46 | def OpenScanCloud(cmd, msg): 47 | from requests import get 48 | osc_user = 'openscan' 49 | osc_pw = 'free' 50 | osc_server = 'http://openscanfeedback.dnsuser.de:1334/' 51 | 52 | try: 53 | r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) 54 | except: 55 | r = type('obj', (object,), {'status_code' : 404, 'text':None}) 56 | return r 57 | 58 | def camera(cmd, msg = {}): 59 | from requests import get 60 | flask = 'http://127.0.0.1:1312/' 61 | try: 62 | r = get(flask + cmd, params=msg) 63 | return r.status_code 64 | except: 65 | return 400 66 | 67 | def motorrun(motor,angle,ES_enable=False,ES_start_state = True): 68 | #motor can be "rotor", "tt" or "extra" 69 | import RPi.GPIO as GPIO 70 | from time import sleep 71 | from math import cos 72 | msg = {'cmd':'set'} 73 | camera('/ping', msg) 74 | 75 | GPIO.setwarnings(False) 76 | GPIO.setmode(GPIO.BCM) 77 | 78 | spr = load_int(motor + '_stepsperrotation') 79 | dirpin = load_int('pin_' + motor + '_dir') 80 | steppin = load_int('pin_' + motor +'_step') 81 | ES_pin = load_int('pin_' + motor + '_endstop') 82 | dir = load_int(motor + '_dir') 83 | ramp = load_int(motor + '_accramp') 84 | acc = load_float(motor + '_acc') 85 | delay_init = load_float(motor + '_delay') 86 | delay = delay_init 87 | 88 | step_count=int(angle*spr/360) * dir 89 | GPIO.setup(dirpin, GPIO.OUT) 90 | GPIO.setup(steppin, GPIO.OUT) 91 | GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) 92 | 93 | if (step_count>0): 94 | GPIO.output(dirpin, GPIO.HIGH) 95 | if(step_count<0): 96 | GPIO.output(dirpin, GPIO.LOW) 97 | step_count=-step_count 98 | for x in range(step_count): 99 | if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: 100 | break 101 | GPIO.output(steppin, GPIO.HIGH) 102 | if x<=ramp and x<=step_count/2: 103 | delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) 104 | #delay=delay_init+(ramp-x)*(delay_init)/acc 105 | elif step_count-x<=ramp and x>step_count/2: 106 | delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) 107 | #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc 108 | else: 109 | delay = delay_init 110 | sleep(delay) 111 | GPIO.output(steppin, GPIO.LOW) 112 | sleep(delay) 113 | 114 | def ringlight(number,state): 115 | import RPi.GPIO as GPIO 116 | msg = {'cmd':'set'} 117 | camera('/ping', msg) 118 | pin = load_int('pin_ringlight' + str(number)) 119 | GPIO.setwarnings(False) 120 | GPIO.setmode(GPIO.BCM) 121 | GPIO.setup(pin, GPIO.OUT) 122 | GPIO.output(pin, state) 123 | 124 | def take_photo(file): 125 | from os import system 126 | filepath = basepath + file 127 | 128 | model=load_str('model') 129 | 130 | 131 | 132 | shutter = str(load_int('cam_shutter')) 133 | saturation = load_str('cam_saturation') 134 | contrast = load_str('cam_contrast') 135 | awbg_red = load_str('cam_awbg_red') 136 | awbg_blue = load_str('cam_awbg_blue') 137 | gain = load_str('cam_gain') 138 | quality = load_int('cam_jpeg_quality') 139 | filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' 140 | #width = load_str('cam_resx') 141 | #height = load_str('cam_resy') 142 | timeout = load_str('cam_timeout') 143 | cropx = load_int('cam_cropx')/200 144 | cropy = load_int('cam_cropy')/200 145 | rotation = load_int('cam_rotation') 146 | AF = load_bool('cam_AFmode') 147 | camera = load_str('camera') 148 | 149 | 150 | if camera == 'imx519' and AF == True: 151 | autofocus = ' --autofocus ' 152 | else: 153 | autofocus = '' 154 | 155 | if camera == "usb_webcam": 156 | cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 157 | else: 158 | 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' 159 | # 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 160 | 161 | system(cmd) 162 | return cmd 163 | 164 | def get_points(samples=1): 165 | from math import pi, sqrt, acos, atan2, cos, sin 166 | 167 | points = [] 168 | phi = pi * (3. - sqrt(5.)) 169 | for i in range(int(samples)): 170 | y = 1 - (i / float(samples - 1)) * 2 171 | radius = sqrt(1 - y * y) 172 | theta = phi * i 173 | x = cos(theta) * radius 174 | z = sin(theta) * radius 175 | r=sqrt(x*x+y*y+z*z) 176 | theta_neu=acos(z/r)*180/pi 177 | phi_neu=atan2(y,x)*180/pi 178 | points.append((theta_neu-90,phi_neu)) 179 | points.sort() 180 | return points 181 | 182 | def create_coordinates(angle_min, angle_max,point_count): 183 | point_count_final=point_count 184 | if angle_max < angle_min: 185 | a = angle_min 186 | angle_min = angle_max 187 | angle_max = a 188 | point_count=point_count*90/(angle_max-angle_min) 189 | actual_points=0 190 | while actual_pointsangle_min and x20: 199 | point_count=point_count+3 200 | else: 201 | point_count=point_count+1 202 | return filtered 203 | 204 | -------------------------------------------------------------------------------- /update/beta/config.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rpf.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | 6 | # uncomment if you get no picture on HDMI for a default "safe" mode 7 | #hdmi_safe=1 8 | hdmi_blanking=2 9 | 10 | # uncomment the following to adjust overscan. Use positive numbers if console 11 | # goes off screen, and negative if there is too much border 12 | #overscan_left=16 13 | #overscan_right=16 14 | #overscan_top=16 15 | #overscan_bottom=16 16 | 17 | # uncomment to force a console size. By default it will be display's size minus 18 | # overscan. 19 | #framebuffer_width=1280 20 | #framebuffer_height=720 21 | 22 | # uncomment if hdmi display is not detected and composite is being output 23 | #hdmi_force_hotplug=1 24 | 25 | # uncomment to force a specific HDMI mode (this will force VGA) 26 | #hdmi_group=1 27 | #hdmi_mode=1 28 | 29 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 30 | # DMT (computer monitor) modes 31 | #hdmi_drive=2 32 | 33 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 34 | # no display 35 | #config_hdmi_boost=4 36 | 37 | # uncomment for composite PAL 38 | #sdtv_mode=2 39 | 40 | #uncomment to overclock the arm. 700 MHz is the default. 41 | #arm_freq=800 42 | 43 | # Uncomment some or all of these to enable the optional hardware interfaces 44 | #dtparam=i2c_arm=on 45 | #dtparam=i2s=on 46 | #dtparam=spi=on 47 | 48 | # Uncomment this to enable infrared communication. 49 | #dtoverlay=gpio-ir,gpio_pin=17 50 | #dtoverlay=gpio-ir-tx,gpio_pin=18 51 | 52 | # Additional overlays and parameters are documented /boot/overlays/README 53 | 54 | # Enable audio (loads snd_bcm2835) 55 | dtparam=audio=on 56 | 57 | # Automatically load overlays for detected cameras 58 | camera_auto_detect=0 59 | 60 | # Automatically load overlays for detected DSI displays 61 | display_auto_detect=1 62 | 63 | # Enable DRM VC4 V3D driver 64 | #dtoverlay=vc4-kms-v3d 65 | max_framebuffers=2 66 | 67 | # Disable compensation for displays with overscan 68 | disable_overscan=1 69 | 70 | [cm4] 71 | # Enable host mode on the 2711 built-in XHCI USB controller. 72 | # This line should be removed if the legacy DWC2 controller is required 73 | # (e.g. for USB device mode) or if USB support is not required. 74 | otg_mode=1 75 | 76 | [pi4] 77 | # Run as fast as firmware / board allows 78 | arm_boost=1 79 | 80 | [all] 81 | 82 | camera_auto_detect=0 83 | gpu_mem=256 84 | dtoverlay=vc4-fkms-v3d 85 | dtoverlay=imx519,media-controller=1 86 | -------------------------------------------------------------------------------- /update/beta/fla.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, make_response, jsonify, request, abort 2 | from PIL import Image 3 | import gphoto2 as gp 4 | from time import sleep, time 5 | import shutil 6 | from OpenScan import load_int, load_float, load_bool, ringlight 7 | import RPi.GPIO as GPIO 8 | from math import sqrt 9 | import os 10 | 11 | GPIO.setwarnings(False) 12 | GPIO.setmode(GPIO.BCM) 13 | 14 | app = Flask(__name__) 15 | 16 | basedir = '/home/pi/OpenScan/' 17 | timer = time() 18 | 19 | ################################################################################################################### 20 | @app.route('/shutdown', methods=['get']) 21 | def shutdown(): 22 | delay = 0.1 23 | ringlight(2,False) 24 | 25 | for i in range (5): 26 | ringlight(1,True) 27 | sleep(delay) 28 | ringlight(1,False) 29 | sleep(delay) 30 | os.system('shutdown -h now') 31 | ################################################################################################################### 32 | @app.route('/reboot', methods=['get']) 33 | def reboot(): 34 | delay = 0.1 35 | ringlight(2,False) 36 | 37 | for i in range (5): 38 | ringlight(1,True) 39 | sleep(delay) 40 | ringlight(1,False) 41 | sleep(delay) 42 | 43 | os.system('reboot -h') 44 | ################################################################################################################### 45 | @app.route('/ping', methods=['get']) 46 | def ping(): 47 | global timer 48 | cmd = str(request.args.get('cmd')) 49 | if cmd == 'set': 50 | timer = time() 51 | inactive = time() - timer 52 | return ({'inactive':inactive}, 200) 53 | ################################################################################################################### 54 | @app.route('/gphoto_init', methods=['get']) 55 | def gphoto_init(): 56 | global camera 57 | camera = gp.Camera() 58 | camera.init() 59 | return ({}, 200) 60 | ################################################################################################################### 61 | @app.route('/gphoto_preview', methods=['get']) 62 | def gphoto_preview(): 63 | filepath = str(request.args.get('filepath')) 64 | camera_file = gp.gp_camera_capture_preview(camera)[1] 65 | target = basedir + filepath 66 | camera_file.save(target) 67 | return ({}, 200) 68 | ################################################################################################################### 69 | @app.route('/gphoto_capture', methods=['get']) 70 | def gphoto_capture(): 71 | filepath = str(request.args.get('filepath')) 72 | file_path = camera.capture(gp.GP_CAPTURE_IMAGE) 73 | camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) 74 | camera_file.save(basedir + filepath) 75 | return ({}, 200) 76 | ################################################################################################################### 77 | @app.route('/gphoto_test', methods=['get']) 78 | def gphoto_test(): 79 | text = camera.get_summary() 80 | return ({}, 200) 81 | ################################################################################################################### 82 | @app.route('/gphoto_exit', methods=['get']) 83 | def gphoto_exit(): 84 | global camera 85 | camera.exit() 86 | return ({}, 200) 87 | ################################################################################################################### 88 | @app.route('/crop', methods=['get']) 89 | def crop(): 90 | output_downscale = load_bool('cam_output_downscale') 91 | output_resolution = load_int('cam_output_resolution') 92 | preview_resolution = load_int('cam_preview_resolution') 93 | filepath_in = basedir + str(request.args.get('filepath_in')) 94 | filepath_out = basedir + str(request.args.get('filepath_out')) 95 | cropx = int(request.args.get('cropx'))/200 96 | cropy = int(request.args.get('cropy'))/200 97 | rotation = int(request.args.get('rotation')) 98 | preview = str(request.args.get('preview')) 99 | downscale = 1 100 | 101 | with Image.open(filepath_in) as img: 102 | w,h = img.size 103 | if cropx != 0 or cropy != 0: 104 | img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) 105 | if rotation == 90: 106 | img = img.transpose(Image.ROTATE_90) 107 | elif rotation == 180: 108 | img= img.transpose(Image.ROTATE_180) 109 | elif rotation == 270: 110 | img= img.transpose(Image.ROTATE_270) 111 | 112 | if preview == "True": 113 | w,h = img.size 114 | factor = (w*h)/preview_resolution 115 | if factor > 1: 116 | img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) 117 | 118 | elif output_downscale == True: 119 | w,h = img.size 120 | factor = (w*h)/output_resolution 121 | if factor > 1: 122 | img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) 123 | 124 | img.save(filepath_out, quality=95, subsampling=0) 125 | 126 | return ({}, 200) 127 | 128 | ################################################################################################################### 129 | @app.route('/external_capture', methods=['get']) 130 | def external_capture(): 131 | pin = load_int('pin_external') 132 | delay_before = load_float('cam_delay_before') 133 | timeout = load_float('cam_timeout')/1000 134 | delay_after = load_float('cam_delay_after') 135 | GPIO.setup(pin, GPIO.OUT) 136 | GPIO.output(pin, GPIO.LOW) 137 | sleep(delay_before) 138 | GPIO.output(pin, GPIO.HIGH) 139 | sleep(timeout) 140 | GPIO.output(pin, GPIO.LOW) 141 | sleep(delay_after) 142 | return ({}, 200) 143 | 144 | 145 | 146 | 147 | if __name__ == '__main__': 148 | # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) 149 | app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) 150 | -------------------------------------------------------------------------------- /update/beta/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node-RED Settings created at Mon, 24 Jan 2022 08:17:31 GMT 3 | * 4 | * It can contain any valid JavaScript code that will get run when Node-RED 5 | * is started. 6 | * 7 | * Lines that start with // are commented out. 8 | * Each entry should be separated from the entries above and below by a comma ',' 9 | * 10 | * For more information about individual settings, refer to the documentation: 11 | * https://nodered.org/docs/user-guide/runtime/configuration 12 | * 13 | * The settings are split into the following sections: 14 | * - Flow File and User Directory Settings 15 | * - Security 16 | * - Server Settings 17 | * - Runtime Settings 18 | * - Editor Settings 19 | * - Node Settings 20 | * 21 | **/ 22 | 23 | module.exports = { 24 | 25 | /******************************************************************************* 26 | * Flow File and User Directory Settings 27 | * - flowFile 28 | * - credentialSecret 29 | * - flowFilePretty 30 | * - userDir 31 | * - nodesDir 32 | ******************************************************************************/ 33 | 34 | /** The file containing the flows. If not set, defaults to flows_.json **/ 35 | flowFile: "flows.json", 36 | 37 | /** By default, credentials are encrypted in storage using a generated key. To 38 | * specify your own secret, set the following property. 39 | * If you want to disable encryption of credentials, set this property to false. 40 | * Note: once you set this property, do not change it - doing so will prevent 41 | * node-red from being able to decrypt your existing credentials and they will be 42 | * lost. 43 | */ 44 | credentialSecret: false, 45 | 46 | /** By default, the flow JSON will be formatted over multiple lines making 47 | * it easier to compare changes when using version control. 48 | * To disable pretty-printing of the JSON set the following property to false. 49 | */ 50 | flowFilePretty: true, 51 | 52 | /** By default, all user data is stored in a directory called `.node-red` under 53 | * the user's home directory. To use a different location, the following 54 | * property can be used 55 | */ 56 | //userDir: '/home/nol/.node-red/', 57 | userDir: '/home/pi/OpenScan/settings/.node-red/', 58 | /** Node-RED scans the `nodes` directory in the userDir to find local node files. 59 | * The following property can be used to specify an additional directory to scan. 60 | */ 61 | //nodesDir: '/home/nol/.node-red/nodes', 62 | 63 | /******************************************************************************* 64 | * Security 65 | * - adminAuth 66 | * - https 67 | * - httpsRefreshInterval 68 | * - requireHttps 69 | * - httpNodeAuth 70 | * - httpStaticAuth 71 | ******************************************************************************/ 72 | 73 | /** To password protect the Node-RED editor and admin API, the following 74 | * property can be used. See http://nodered.org/docs/security.html for details. 75 | */ 76 | //adminAuth: { 77 | // type: "credentials", 78 | // users: [{ 79 | // username: "admin", 80 | // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", 81 | // permissions: "*" 82 | // }] 83 | //}, 84 | 85 | /** The following property can be used to enable HTTPS 86 | * This property can be either an object, containing both a (private) key 87 | * and a (public) certificate, or a function that returns such an object. 88 | * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener 89 | * for details of its contents. 90 | */ 91 | 92 | /** Option 1: static object */ 93 | //https: { 94 | // key: require("fs").readFileSync('privkey.pem'), 95 | // cert: require("fs").readFileSync('cert.pem') 96 | //}, 97 | 98 | /** Option 2: function that returns the HTTP configuration object */ 99 | // https: function() { 100 | // // This function should return the options object, or a Promise 101 | // // that resolves to the options object 102 | // return { 103 | // key: require("fs").readFileSync('privkey.pem'), 104 | // cert: require("fs").readFileSync('cert.pem') 105 | // } 106 | // }, 107 | 108 | /** If the `https` setting is a function, the following setting can be used 109 | * to set how often, in hours, the function will be called. That can be used 110 | * to refresh any certificates. 111 | */ 112 | //httpsRefreshInterval : 12, 113 | 114 | /** The following property can be used to cause insecure HTTP connections to 115 | * be redirected to HTTPS. 116 | */ 117 | //requireHttps: true, 118 | 119 | /** To password protect the node-defined HTTP endpoints (httpNodeRoot), 120 | * including node-red-dashboard, or the static content (httpStatic), the 121 | * following properties can be used. 122 | * The `pass` field is a bcrypt hash of the password. 123 | * See http://nodered.org/docs/security.html#generating-the-password-hash 124 | */ 125 | //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 126 | //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 127 | 128 | /******************************************************************************* 129 | * Server Settings 130 | * - uiPort 131 | * - uiHost 132 | * - apiMaxLength 133 | * - httpServerOptions 134 | * - httpAdminRoot 135 | * - httpAdminMiddleware 136 | * - httpNodeRoot 137 | * - httpNodeCors 138 | * - httpNodeMiddleware 139 | * - httpStatic 140 | ******************************************************************************/ 141 | 142 | /** the tcp port that the Node-RED web server is listening on */ 143 | // uiPort: process.env.PORT || 1880, 144 | uiPort: process.env.PORT || 80, 145 | /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. 146 | * To listen on all IPv6 addresses, set uiHost to "::", 147 | * The following property can be used to listen on a specific interface. For 148 | * example, the following would only allow connections from the local machine. 149 | */ 150 | //uiHost: "127.0.0.1", 151 | 152 | /** The maximum size of HTTP request that will be accepted by the runtime api. 153 | * Default: 5mb 154 | */ 155 | //apiMaxLength: '5mb', 156 | 157 | /** The following property can be used to pass custom options to the Express.js 158 | * server used by Node-RED. For a full list of available options, refer 159 | * to http://expressjs.com/en/api.html#app.settings.table 160 | */ 161 | //httpServerOptions: { }, 162 | 163 | /** By default, the Node-RED UI is available at http://localhost:1880/ 164 | * The following property can be used to specify a different root path. 165 | * If set to false, this is disabled. 166 | */ 167 | //httpAdminRoot: '/admin', 168 | httpAdminRoot: '/editor', 169 | /** The following property can be used to add a custom middleware function 170 | * in front of all admin http routes. For example, to set custom http 171 | * headers. It can be a single function or an array of middleware functions. 172 | */ 173 | // httpAdminMiddleware: function(req,res,next) { 174 | // // Set the X-Frame-Options header to limit where the editor 175 | // // can be embedded 176 | // //res.set('X-Frame-Options', 'sameorigin'); 177 | // next(); 178 | // }, 179 | 180 | 181 | /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. 182 | * By default, these are served relative to '/'. The following property 183 | * can be used to specifiy a different root path. If set to false, this is 184 | * disabled. 185 | */ 186 | //httpNodeRoot: '/red-nodes', 187 | 188 | /** The following property can be used to configure cross-origin resource sharing 189 | * in the HTTP nodes. 190 | * See https://github.com/troygoode/node-cors#configuration-options for 191 | * details on its contents. The following is a basic permissive set of options: 192 | */ 193 | //httpNodeCors: { 194 | // origin: "*", 195 | // methods: "GET,PUT,POST,DELETE" 196 | //}, 197 | 198 | /** If you need to set an http proxy please set an environment variable 199 | * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. 200 | * For example - http_proxy=http://myproxy.com:8080 201 | * (Setting it here will have no effect) 202 | * You may also specify no_proxy (or NO_PROXY) to supply a comma separated 203 | * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk 204 | */ 205 | 206 | /** The following property can be used to add a custom middleware function 207 | * in front of all http in nodes. This allows custom authentication to be 208 | * applied to all http in nodes, or any other sort of common request processing. 209 | * It can be a single function or an array of middleware functions. 210 | */ 211 | //httpNodeMiddleware: function(req,res,next) { 212 | // // Handle/reject the request, or pass it on to the http in node by calling next(); 213 | // // Optionally skip our rawBodyParser by setting this to true; 214 | // //req.skipRawBodyParser = true; 215 | // next(); 216 | //}, 217 | 218 | /** When httpAdminRoot is used to move the UI to a different root path, the 219 | * following property can be used to identify a directory of static content 220 | * that should be served at http://localhost:1880/. 221 | */ 222 | //httpStatic: '/home/nol/node-red-static/', 223 | httpStatic: '/home/pi/OpenScan/', 224 | /******************************************************************************* 225 | * Runtime Settings 226 | * - lang 227 | * - logging 228 | * - contextStorage 229 | * - exportGlobalContextKeys 230 | * - externalModules 231 | ******************************************************************************/ 232 | 233 | /** Uncomment the following to run node-red in your preferred language. 234 | * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko 235 | * Some languages are more complete than others. 236 | */ 237 | // lang: "de", 238 | 239 | /** Configure the logging output */ 240 | logging: { 241 | /** Only console logging is currently supported */ 242 | console: { 243 | /** Level of logging to be recorded. Options are: 244 | * fatal - only those errors which make the application unusable should be recorded 245 | * error - record errors which are deemed fatal for a particular request + fatal errors 246 | * warn - record problems which are non fatal + errors + fatal errors 247 | * info - record information about the general running of the application + warn + error + fatal errors 248 | * debug - record information which is more verbose than info + info + warn + error + fatal errors 249 | * trace - record very detailed logging + debug + info + warn + error + fatal errors 250 | * off - turn off all logging (doesn't affect metrics or audit) 251 | */ 252 | level: "info", 253 | /** Whether or not to include metric events in the log output */ 254 | metrics: false, 255 | /** Whether or not to include audit events in the log output */ 256 | audit: false 257 | } 258 | }, 259 | 260 | /** Context Storage 261 | * The following property can be used to enable context storage. The configuration 262 | * provided here will enable file-based context that flushes to disk every 30 seconds. 263 | * Refer to the documentation for further options: https://nodered.org/docs/api/context/ 264 | */ 265 | //contextStorage: { 266 | // default: { 267 | // module:"localfilesystem" 268 | // }, 269 | //}, 270 | 271 | /** `global.keys()` returns a list of all properties set in global context. 272 | * This allows them to be displayed in the Context Sidebar within the editor. 273 | * In some circumstances it is not desirable to expose them to the editor. The 274 | * following property can be used to hide any property set in `functionGlobalContext` 275 | * from being list by `global.keys()`. 276 | * By default, the property is set to false to avoid accidental exposure of 277 | * their values. Setting this to true will cause the keys to be listed. 278 | */ 279 | exportGlobalContextKeys: false, 280 | 281 | /** Configure how the runtime will handle external npm modules. 282 | * This covers: 283 | * - whether the editor will allow new node modules to be installed 284 | * - whether nodes, such as the Function node are allowed to have their 285 | * own dynamically configured dependencies. 286 | * The allow/denyList options can be used to limit what modules the runtime 287 | * will install/load. It can use '*' as a wildcard that matches anything. 288 | */ 289 | externalModules: { 290 | // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ 291 | // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ 292 | // palette: { /** Configuration for the Palette Manager */ 293 | // allowInstall: true, /** Enable the Palette Manager in the editor */ 294 | // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ 295 | // allowList: [], 296 | // denyList: [] 297 | // }, 298 | // modules: { /** Configuration for node-specified modules */ 299 | // allowInstall: true, 300 | // allowList: [], 301 | // denyList: [] 302 | // } 303 | }, 304 | 305 | 306 | /******************************************************************************* 307 | * Editor Settings 308 | * - disableEditor 309 | * - editorTheme 310 | ******************************************************************************/ 311 | 312 | /** The following property can be used to disable the editor. The admin API 313 | * is not affected by this option. To disable both the editor and the admin 314 | * API, use either the httpRoot or httpAdminRoot properties 315 | */ 316 | //disableEditor: false, 317 | 318 | /** Customising the editor 319 | * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes 320 | * for all available options. 321 | */ 322 | editorTheme: { 323 | /** The following property can be used to set a custom theme for the editor. 324 | * See https://github.com/node-red-contrib-themes/theme-collection for 325 | * a collection of themes to chose from. 326 | */ 327 | //theme: "", 328 | palette: { 329 | /** The following property can be used to order the categories in the editor 330 | * palette. If a node's category is not in the list, the category will get 331 | * added to the end of the palette. 332 | * If not set, the following default order is used: 333 | */ 334 | //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], 335 | }, 336 | projects: { 337 | /** To enable the Projects feature, set this value to true */ 338 | enabled: false, 339 | workflow: { 340 | /** Set the default projects workflow mode. 341 | * - manual - you must manually commit changes 342 | * - auto - changes are automatically committed 343 | * This can be overridden per-user from the 'Git config' 344 | * section of 'User Settings' within the editor 345 | */ 346 | mode: "manual" 347 | } 348 | }, 349 | codeEditor: { 350 | /** Select the text editor component used by the editor. 351 | * Defaults to "ace", but can be set to "ace" or "monaco" 352 | */ 353 | lib: "ace", 354 | options: { 355 | /** The follow options only apply if the editor is set to "monaco" 356 | * 357 | * theme - must match the file name of a theme in 358 | * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme 359 | * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" 360 | */ 361 | theme: "vs", 362 | /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. 363 | * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html 364 | */ 365 | //fontSize: 14, 366 | //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", 367 | //fontLigatures: true, 368 | } 369 | } 370 | }, 371 | 372 | /******************************************************************************* 373 | * Node Settings 374 | * - fileWorkingDirectory 375 | * - functionGlobalContext 376 | * - functionExternalModules 377 | * - nodeMessageBufferMaxLength 378 | * - ui (for use with Node-RED Dashboard) 379 | * - debugUseColors 380 | * - debugMaxLength 381 | * - execMaxBufferSize 382 | * - httpRequestTimeout 383 | * - mqttReconnectTime 384 | * - serialReconnectTime 385 | * - socketReconnectTime 386 | * - socketTimeout 387 | * - tcpMsgQueueSize 388 | * - inboundWebSocketTimeout 389 | * - tlsConfigDisableLocalFiles 390 | * - webSocketNodeVerifyClient 391 | ******************************************************************************/ 392 | 393 | /** The working directory to handle relative file paths from within the File nodes 394 | * defaults to the working directory of the Node-RED process. 395 | */ 396 | //fileWorkingDirectory: "", 397 | 398 | /** Allow the Function node to load additional npm modules directly */ 399 | functionExternalModules: true, 400 | 401 | /** The following property can be used to set predefined values in Global Context. 402 | * This allows extra node modules to be made available with in Function node. 403 | * For example, the following: 404 | * functionGlobalContext: { os:require('os') } 405 | * will allow the `os` module to be accessed in a Function node using: 406 | * global.get("os") 407 | */ 408 | functionGlobalContext: { 409 | os:require('os'), 410 | path:require('path'), 411 | fs:require('fs'), 412 | 413 | }, 414 | 415 | /** The maximum number of messages nodes will buffer internally as part of their 416 | * operation. This applies across a range of nodes that operate on message sequences. 417 | * defaults to no limit. A value of 0 also means no limit is applied. 418 | */ 419 | //nodeMessageBufferMaxLength: 0, 420 | 421 | /** If you installed the optional node-red-dashboard you can set it's path 422 | * relative to httpNodeRoot 423 | * Other optional properties include 424 | * readOnly:{boolean}, 425 | * middleware:{function or array}, (req,res,next) - http middleware 426 | * ioMiddleware:{function or array}, (socket,next) - socket.io middleware 427 | */ 428 | //ui: { path: "ui" }, 429 | ui: { path: "" }, 430 | /** Colourise the console output of the debug node */ 431 | //debugUseColors: true, 432 | 433 | /** The maximum length, in characters, of any message sent to the debug sidebar tab */ 434 | debugMaxLength: 1000, 435 | 436 | /** Maximum buffer size for the exec node. Defaults to 10Mb */ 437 | //execMaxBufferSize: 10000000, 438 | 439 | /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ 440 | //httpRequestTimeout: 120000, 441 | 442 | /** Retry time in milliseconds for MQTT connections */ 443 | mqttReconnectTime: 15000, 444 | 445 | /** Retry time in milliseconds for Serial port connections */ 446 | serialReconnectTime: 15000, 447 | 448 | /** Retry time in milliseconds for TCP socket connections */ 449 | //socketReconnectTime: 10000, 450 | 451 | /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ 452 | //socketTimeout: 120000, 453 | 454 | /** Maximum number of messages to wait in queue while attempting to connect to TCP socket 455 | * defaults to 1000 456 | */ 457 | //tcpMsgQueueSize: 2000, 458 | 459 | /** Timeout in milliseconds for inbound WebSocket connections that do not 460 | * match any configured node. Defaults to 5000 461 | */ 462 | //inboundWebSocketTimeout: 5000, 463 | 464 | /** To disable the option for using local files for storing keys and 465 | * certificates in the TLS configuration node, set this to true. 466 | */ 467 | //tlsConfigDisableLocalFiles: true, 468 | 469 | /** The following property can be used to verify websocket connection attempts. 470 | * This allows, for example, the HTTP request headers to be checked to ensure 471 | * they include valid authentication information. 472 | */ 473 | //webSocketNodeVerifyClient: function(info) { 474 | // /** 'info' has three properties: 475 | // * - origin : the value in the Origin header 476 | // * - req : the HTTP request 477 | // * - secure : true if req.connection.authorized or req.connection.encrypted is set 478 | // * 479 | // * The function should return true if the connection should be accepted, false otherwise. 480 | // * 481 | // * Alternatively, if this function is defined to accept a second argument, callback, 482 | // * it can be used to verify the client asynchronously. 483 | // * The callback takes three arguments: 484 | // * - result : boolean, whether to accept the connection or not 485 | // * - code : if result is false, the HTTP error status to return 486 | // * - reason: if result is false, the HTTP reason string to return 487 | // */ 488 | //}, 489 | } 490 | -------------------------------------------------------------------------------- /update/betaArdu/Arducam.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | 4 | try: 5 | import v4l2 6 | except Exception as e: 7 | print(e) 8 | print("Try to install v4l2-fix") 9 | try: 10 | from pip import main as pipmain 11 | except ImportError: 12 | from pip._internal import main as pipmain 13 | pipmain(['install', 'v4l2-fix']) 14 | print("\nTry to run the focus program again.") 15 | exit(0) 16 | 17 | import fcntl 18 | import errno 19 | 20 | # # Type 21 | # v4l2.V4L2_CTRL_TYPE_INTEGER 22 | # v4l2.V4L2_CTRL_TYPE_BOOLEAN 23 | # v4l2.V4L2_CTRL_TYPE_MENU 24 | # v4l2.V4L2_CTRL_TYPE_BUTTON 25 | # v4l2.V4L2_CTRL_TYPE_INTEGER64 26 | # v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 27 | # # Flags 28 | # v4l2.V4L2_CTRL_FLAG_DISABLED 29 | # v4l2.V4L2_CTRL_FLAG_GRABBED 30 | # v4l2.V4L2_CTRL_FLAG_READ_ONLY 31 | # v4l2.V4L2_CTRL_FLAG_UPDATE 32 | # v4l2.V4L2_CTRL_FLAG_INACTIVE 33 | # v4l2.V4L2_CTRL_FLAG_SLIDER 34 | 35 | def assert_valid_queryctrl(queryctrl): 36 | return queryctrl.type & ( 37 | v4l2.V4L2_CTRL_TYPE_INTEGER 38 | | v4l2.V4L2_CTRL_TYPE_BOOLEAN 39 | | v4l2.V4L2_CTRL_TYPE_MENU 40 | | v4l2.V4L2_CTRL_TYPE_BUTTON 41 | | v4l2.V4L2_CTRL_TYPE_INTEGER64 42 | | v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 43 | | 7 44 | | 8 45 | | 9 46 | ) and queryctrl.flags & ( 47 | v4l2.V4L2_CTRL_FLAG_DISABLED 48 | | v4l2.V4L2_CTRL_FLAG_GRABBED 49 | | v4l2.V4L2_CTRL_FLAG_READ_ONLY 50 | | v4l2.V4L2_CTRL_FLAG_UPDATE 51 | | v4l2.V4L2_CTRL_FLAG_INACTIVE 52 | | v4l2.V4L2_CTRL_FLAG_SLIDER 53 | ) 54 | 55 | def get_device_controls_menu(fd, queryctrl): 56 | querymenu = v4l2.v4l2_querymenu(queryctrl.id, queryctrl.minimum) 57 | while querymenu.index <= queryctrl.maximum: 58 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYMENU, querymenu) 59 | yield querymenu 60 | querymenu.index += 1 61 | 62 | def get_device_controls_by_class(fd, control_class): 63 | # enumeration by control class 64 | queryctrl = v4l2.v4l2_queryctrl(control_class | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 65 | while True: 66 | try: 67 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 68 | except IOError as e: 69 | assert e.errno == errno.EINVAL 70 | break 71 | if v4l2.V4L2_CTRL_ID2CLASS(queryctrl.id) != control_class: 72 | break 73 | yield queryctrl 74 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 75 | 76 | def getdict(struct): 77 | val = dict((field, getattr(struct, field)) for field, _ in struct._fields_) 78 | val.pop("reserved") 79 | return val 80 | 81 | def get_device_controls(fd): 82 | # original enumeration method 83 | queryctrl = v4l2.v4l2_queryctrl(v4l2.V4L2_CID_BASE) 84 | while queryctrl.id < v4l2.V4L2_CID_LASTP1: 85 | try: 86 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 87 | print(queryctrl.name) 88 | except IOError as e: 89 | # this predefined control is not supported by this device 90 | assert e.errno == errno.EINVAL 91 | queryctrl.id += 1 92 | continue 93 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id + 1) 94 | 95 | def get_ctrls(vd): 96 | ctrls = [] 97 | # enumeration by control class 98 | for class_ in (v4l2.V4L2_CTRL_CLASS_USER, v4l2.V4L2_CTRL_CLASS_MPEG, v4l2.V4L2_CTRL_CLASS_CAMERA): 99 | for queryctrl in get_device_controls_by_class(vd, class_): 100 | ctrl = getdict(queryctrl) 101 | if queryctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: 102 | ctrl["menu"] = [] 103 | for querymenu in get_device_controls_menu(vd, queryctrl): 104 | # print(querymenu.name) 105 | ctrl["menu"].append(querymenu.name) 106 | 107 | if queryctrl.type == 9: 108 | ctrl["menu"] = [] 109 | for querymenu in get_device_controls_menu(vd, queryctrl): 110 | ctrl["menu"].append(querymenu.index) 111 | ctrls.append(ctrl) 112 | return ctrls 113 | 114 | def set_ctrl(vd, id, value): 115 | ctrl = v4l2.v4l2_control() 116 | ctrl.id = id 117 | ctrl.value = value 118 | try: 119 | fcntl.ioctl(vd, v4l2.VIDIOC_S_CTRL, ctrl) 120 | except IOError as e: 121 | print(e) 122 | 123 | def get_ctrl(vd, id): 124 | ctrl = v4l2.v4l2_control() 125 | ctrl.id = id 126 | try: 127 | fcntl.ioctl(vd, v4l2.VIDIOC_G_CTRL, ctrl) 128 | except IOError as e: 129 | print(e) 130 | return None 131 | return ctrl.value 132 | 133 | 134 | class Focuser: 135 | FOCUS_ID = 0x009a090a 136 | dev = None 137 | 138 | def __init__(self, dev=0): 139 | self.focus_value = 0 140 | self.dev = dev 141 | 142 | if type(dev) == int or (type(dev) == str and dev.isnumeric()): 143 | self.dev = "/dev/video{}".format(dev) 144 | 145 | self.fd = open(self.dev, 'r') 146 | self.ctrls = get_ctrls(self.fd) 147 | self.hasFocus = False 148 | for ctrl in self.ctrls: 149 | if ctrl['id'] == Focuser.FOCUS_ID: 150 | self.hasFocus = True 151 | self.opts[Focuser.OPT_FOCUS]["MIN_VALUE"] = ctrl['minimum'] 152 | self.opts[Focuser.OPT_FOCUS]["MAX_VALUE"] = ctrl['maximum'] 153 | self.opts[Focuser.OPT_FOCUS]["DEF_VALUE"] = ctrl['default'] 154 | self.focus_value = get_ctrl(self.fd, Focuser.FOCUS_ID) 155 | 156 | if not self.hasFocus: 157 | raise RuntimeError("Device {} has no focus_absolute control.".format(self.dev)) 158 | 159 | def read(self): 160 | return self.focus_value 161 | 162 | def write(self, value): 163 | self.focus_value = value 164 | # os.system("v4l2-ctl -d {} -c focus_absolute={}".format(self.dev, value)) 165 | set_ctrl(self.fd, Focuser.FOCUS_ID, value) 166 | 167 | OPT_BASE = 0x1000 168 | OPT_FOCUS = OPT_BASE | 0x01 169 | OPT_ZOOM = OPT_BASE | 0x02 170 | OPT_MOTOR_X = OPT_BASE | 0x03 171 | OPT_MOTOR_Y = OPT_BASE | 0x04 172 | OPT_IRCUT = OPT_BASE | 0x05 173 | opts = { 174 | OPT_FOCUS : { 175 | "MIN_VALUE": 0, 176 | "MAX_VALUE": 1000, 177 | "DEF_VALUE": 0, 178 | }, 179 | } 180 | def reset(self,opt,flag = 1): 181 | info = self.opts[opt] 182 | if info == None or info["DEF_VALUE"] == None: 183 | return 184 | self.set(opt,info["DEF_VALUE"]) 185 | 186 | def get(self,opt,flag = 0): 187 | info = self.opts[opt] 188 | return self.read() 189 | 190 | def set(self,opt,value,flag = 1): 191 | info = self.opts[opt] 192 | if value > info["MAX_VALUE"]: 193 | value = info["MAX_VALUE"] 194 | elif value < info["MIN_VALUE"]: 195 | value = info["MIN_VALUE"] 196 | self.write(value) 197 | print("write: {}".format(value)) 198 | 199 | def __del__(self): 200 | self.fd.close() 201 | 202 | pass 203 | -------------------------------------------------------------------------------- /update/betaArdu/OpenScan.py: -------------------------------------------------------------------------------- 1 | basepath = '/home/pi/OpenScan/' 2 | from os.path import isfile 3 | import os 4 | 5 | def load_bool(name): 6 | filename = basepath+'settings/'+name 7 | if not isfile(filename): 8 | return 9 | with open(filename, 'r') as file: 10 | value = file.read().replace('\n','') 11 | if value == '1' or value == 'True' or value =='true': 12 | value = True 13 | else: 14 | value = False 15 | return value 16 | 17 | def fade_led(pin_led, fade_steps, duty_max, dir = True): 18 | import RPi.GPIO as GPIO 19 | import time 20 | GPIO.setmode(GPIO.BCM) 21 | GPIO.setwarnings(False) 22 | GPIO.setup(pin_led, GPIO.OUT) 23 | pwm = GPIO.PWM(pin_led, 200) 24 | 25 | if dir: 26 | pwm.start(0) 27 | for duty_cycle in range(0, fade_steps*10, 1): # Increase duty cycle in steps 28 | pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) 29 | time.sleep(0.001) # Pause between steps (adjust as needed) 30 | else: 31 | pwm.start(duty_max) 32 | for duty_cycle in range(fade_steps*10,0, -1): # Increase duty cycle in steps 33 | pwm.ChangeDutyCycle(duty_max*duty_cycle/(10*fade_steps)) 34 | time.sleep(0.001) # Pause between steps (adjust as needed) 35 | pwm.stop() 36 | 37 | 38 | def check_hotspot_mode(interface="wlan0"): 39 | import subprocess 40 | try: 41 | output = subprocess.check_output(["iwconfig", interface]).decode("utf-8") 42 | if "Mode:Master" in output: 43 | return True 44 | elif "Mode:Managed" in output: 45 | return False 46 | else: 47 | return False 48 | except subprocess.CalledProcessError as e: 49 | return False 50 | 51 | 52 | 53 | def add_wifi_network(ssid, password, country): 54 | import re 55 | conf_file = "/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" 56 | 57 | if not os.path.exists(conf_file): 58 | return False 59 | 60 | if not (ssid and password and country): 61 | return False 62 | 63 | with open(conf_file, "r") as f: 64 | content = f.read() 65 | 66 | updated_content = re.sub(r'country=\w+', f'country={country}', content) 67 | 68 | if f'ssid="{ssid}"' in content: 69 | network_block_pattern = re.compile( 70 | r'network=\{\s*ssid="' + re.escape(ssid) + r'".*?psk=".*?".*?\}', re.DOTALL 71 | ) 72 | updated_network_block = f'network={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}' 73 | updated_content = network_block_pattern.sub(updated_network_block, updated_content) 74 | else: 75 | network_block = f'\nnetwork={{\n ssid="{ssid}"\n psk="{password}"\n key_mgmt=WPA-PSK\n}}\n' 76 | updated_content += network_block 77 | 78 | with open(conf_file, "w") as f: 79 | f.write(updated_content) 80 | os.system("sudo systemctl restart wpa_supplicant@wlan0") 81 | 82 | return True 83 | 84 | 85 | def load_str(name): 86 | filename = basepath+'settings/'+name 87 | if not isfile(filename): 88 | return 89 | with open(filename, 'r') as file: 90 | value = file.read().replace('\n','') 91 | return value 92 | 93 | def load_int(name): 94 | filename = basepath+'settings/'+name 95 | if not isfile(filename): 96 | return 97 | with open(filename, 'r') as file: 98 | value = int(file.read().replace('\n','')) 99 | return value 100 | 101 | def load_float(name): 102 | filename = basepath+'settings/'+name 103 | if not isfile(filename): 104 | return 105 | with open(filename, 'r') as file: 106 | value = float(file.read().replace('\n','')) 107 | return value 108 | 109 | def save(name, value): 110 | filename = basepath+'settings/'+name 111 | with open(filename, 'w+') as file: 112 | file.write(str(value)) 113 | return 114 | 115 | def OpenScanCloud(cmd, msg): 116 | from requests import get 117 | osc_user = 'openscan' 118 | osc_pw = 'free' 119 | osc_server = 'http://openscanfeedback.dnsuser.de:1334/' 120 | 121 | try: 122 | r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) 123 | except: 124 | r = type('obj', (object,), {'status_code' : 404, 'text':None}) 125 | return r 126 | 127 | def camera(cmd, msg = {}): 128 | from requests import get 129 | flask = 'http://127.0.0.1:1312/' 130 | try: 131 | r = get(flask + cmd, params=msg) 132 | return r.status_code 133 | except: 134 | return 400 135 | 136 | def motorrun(motor,angle,ES_enable=False,ES_start_state = True): 137 | #motor can be "rotor", "tt" or "extra" 138 | import RPi.GPIO as GPIO 139 | from time import sleep 140 | from math import cos 141 | msg = {'cmd':'set'} 142 | 143 | GPIO.setwarnings(False) 144 | GPIO.setmode(GPIO.BCM) 145 | 146 | spr = load_int(motor + '_stepsperrotation') 147 | dirpin = load_int('pin_' + motor + '_dir') 148 | steppin = load_int('pin_' + motor +'_step') 149 | ES_pin = load_int('pin_' + motor + '_endstop') 150 | dir = load_int(motor + '_dir') 151 | ramp = load_int(motor + '_accramp') 152 | acc = load_float(motor + '_acc') 153 | delay_init = load_float(motor + '_delay') 154 | delay = delay_init 155 | 156 | step_count=int(angle*spr/360) * dir 157 | GPIO.setup(dirpin, GPIO.OUT) 158 | GPIO.setup(steppin, GPIO.OUT) 159 | GPIO.setup(ES_pin, GPIO.IN, pull_up_down = GPIO.PUD_UP) 160 | 161 | if (step_count>0): 162 | GPIO.output(dirpin, GPIO.HIGH) 163 | if(step_count<0): 164 | GPIO.output(dirpin, GPIO.LOW) 165 | step_count=-step_count 166 | for x in range(step_count): 167 | if ES_enable == True and GPIO.input(ES_pin) != ES_start_state: 168 | i = 0 169 | while i <= 10: 170 | if GPIO.input(ES_pin) == ES_start_state: 171 | i = 11 172 | if i == 10: 173 | return 174 | i = i + 1 175 | 176 | GPIO.output(steppin, GPIO.HIGH) 177 | if x<=ramp and x<=step_count/2: 178 | delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) 179 | #delay=delay_init+(ramp-x)*(delay_init)/acc 180 | elif step_count-x<=ramp and x>step_count/2: 181 | delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) 182 | #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc 183 | else: 184 | delay = delay_init 185 | sleep(delay) 186 | GPIO.output(steppin, GPIO.LOW) 187 | sleep(delay) 188 | 189 | def ringlight(number,state): 190 | import RPi.GPIO as GPIO 191 | msg = {'cmd':'set'} 192 | pin = load_int('pin_ringlight' + str(number)) 193 | GPIO.setwarnings(False) 194 | GPIO.setmode(GPIO.BCM) 195 | GPIO.setup(pin, GPIO.OUT) 196 | GPIO.output(pin, state) 197 | 198 | def take_photo(file): 199 | from os import system 200 | filepath = basepath + file 201 | 202 | model=load_str('model') 203 | 204 | 205 | 206 | shutter = str(load_int('cam_shutter')) 207 | saturation = load_str('cam_saturation') 208 | contrast = load_str('cam_contrast') 209 | awbg_red = load_str('cam_awbg_red') 210 | awbg_blue = load_str('cam_awbg_blue') 211 | gain = load_str('cam_gain') 212 | quality = load_int('cam_jpeg_quality') 213 | filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' 214 | #width = load_str('cam_resx') 215 | #height = load_str('cam_resy') 216 | timeout = load_str('cam_timeout') 217 | cropx = load_int('cam_cropx')/200 218 | cropy = load_int('cam_cropy')/200 219 | rotation = load_int('cam_rotation') 220 | AF = load_bool('cam_AFmode') 221 | camera = load_str('camera') 222 | 223 | 224 | if camera == 'imx519' and AF == True: 225 | autofocus = ' --autofocus ' 226 | else: 227 | autofocus = '' 228 | 229 | if camera == "usb_webcam": 230 | cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 231 | else: 232 | 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' 233 | # 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 234 | 235 | system(cmd) 236 | return cmd 237 | 238 | def get_points(samples=1): 239 | from math import pi, sqrt, acos, atan2, cos, sin 240 | 241 | points = [] 242 | phi = pi * (3. - sqrt(5.)) 243 | for i in range(int(samples)): 244 | y = 1 - (i / float(samples - 1)) * 2 245 | radius = sqrt(1 - y * y) 246 | theta = phi * i 247 | x = cos(theta) * radius 248 | z = sin(theta) * radius 249 | r=sqrt(x*x+y*y+z*z) 250 | theta_neu=acos(z/r)*180/pi 251 | phi_neu=atan2(y,x)*180/pi 252 | points.append((theta_neu-90,phi_neu)) 253 | points.sort() 254 | return points 255 | 256 | def create_coordinates(angle_min, angle_max,point_count): 257 | point_count_final=point_count 258 | if angle_max < angle_min: 259 | a = angle_min 260 | angle_min = angle_max 261 | angle_max = a 262 | point_count=point_count*90/(angle_max-angle_min) 263 | actual_points=0 264 | while actual_pointsangle_min and x20: 273 | point_count=point_count+3 274 | else: 275 | point_count=point_count+1 276 | return filtered 277 | 278 | 279 | def haversine_distance_deg(theta1, phi1, theta2, phi2): 280 | import numpy as np 281 | R = 1 282 | dtheta = np.radians(theta2 - theta1) 283 | dphi = np.radians(phi2 - phi1) 284 | 285 | theta1, phi1 = np.radians(theta1), np.radians(phi1) 286 | theta2, phi2 = np.radians(theta2), np.radians(phi2) 287 | 288 | a = np.sin(dtheta / 2) ** 2 + np.cos(theta1) * np.cos(theta2) * np.sin(dphi / 2) ** 2 289 | c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) 290 | 291 | return R * c 292 | 293 | def sort_spherical_coordinates_deg(points_spherical_deg): 294 | import numpy as np 295 | from tsp_solver.greedy import solve_tsp 296 | 297 | points_spherical_deg = np.array(points_spherical_deg) # Convert list of tuples to NumPy array 298 | 299 | n = len(points_spherical_deg) 300 | dist_matrix = np.zeros((n, n)) 301 | 302 | # Calculate haversine distance for each pair of points 303 | for i in range(n): 304 | for j in range(i + 1, n): 305 | dist = haversine_distance_deg(points_spherical_deg[i, 0], points_spherical_deg[i, 1], 306 | points_spherical_deg[j, 0], points_spherical_deg[j, 1]) 307 | dist_matrix[i, j] = dist 308 | dist_matrix[j, i] = dist 309 | 310 | # Solve the TSP problem using the tsp_solver.greedy algorithm 311 | path = solve_tsp(dist_matrix) 312 | 313 | sorted_points_spherical_deg = points_spherical_deg[path] 314 | 315 | # Convert the sorted NumPy array back to a list of tuples 316 | return [tuple(point) for point in sorted_points_spherical_deg] 317 | -------------------------------------------------------------------------------- /update/betaArdu/config.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rpf.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | # uncomment if you get no picture on HDMI for a default "safe" mode 6 | #hdmi_safe=1 7 | 8 | # uncomment the following to adjust overscan. Use positive numbers if console 9 | # goes off screen, and negative if there is too much border 10 | #overscan_left=16 11 | #overscan_right=16 12 | #overscan_top=16 13 | #overscan_bottom=16 14 | 15 | # uncomment to force a console size. By default it will be display's size minus 16 | # overscan. 17 | #framebuffer_width=1280 18 | #framebuffer_height=720 19 | 20 | # uncomment if hdmi display is not detected and composite is being output 21 | #hdmi_force_hotplug=1 22 | 23 | # uncomment to force a specific HDMI mode (this will force VGA) 24 | #hdmi_group=1 25 | #hdmi_mode=1 26 | 27 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 28 | # DMT (computer monitor) modes 29 | #hdmi_drive=2 30 | 31 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 32 | # no display 33 | #config_hdmi_boost=4 34 | 35 | # uncomment for composite PAL 36 | #sdtv_mode=2 37 | 38 | #uncomment to overclock the arm. 700 MHz is the default. 39 | #arm_freq=800 40 | 41 | # Uncomment some or all of these to enable the optional hardware interfaces 42 | #dtparam=i2c_arm=on 43 | #dtparam=i2s=on 44 | #dtparam=spi=on 45 | 46 | # Uncomment this to enable infrared communication. 47 | #dtoverlay=gpio-ir,gpio_pin=17 48 | #dtoverlay=gpio-ir-tx,gpio_pin=18 49 | 50 | # Additional overlays and parameters are documented /boot/overlays/README 51 | 52 | # Enable audio (loads snd_bcm2835) 53 | dtparam=audio=on 54 | 55 | # Automatically load overlays for detected cameras 56 | camera_auto_detect=1 57 | 58 | # Automatically load overlays for detected DSI displays 59 | display_auto_detect=1 60 | 61 | # Enable DRM VC4 V3D driver 62 | dtoverlay=vc4-kms-v3d 63 | max_framebuffers=2 64 | 65 | # Disable compensation for displays with overscan 66 | disable_overscan=1 67 | 68 | [cm4] 69 | # Enable host mode on the 2711 built-in XHCI USB controller. 70 | # This line should be removed if the legacy DWC2 controller is required 71 | # (e.g. for USB device mode) or if USB support is not required. 72 | otg_mode=1 73 | 74 | [all] 75 | 76 | [pi4] 77 | # Run as fast as firmware / board allows 78 | arm_boost=1 79 | 80 | [all] 81 | camera_auto_detect=0 82 | gpu_mem=256 83 | dtoverlay=vc4-fkms-v3d 84 | dtoverlay=imx519 85 | #dtoverlay=imx519,media-controller=1 86 | -------------------------------------------------------------------------------- /update/betaArdu/fla.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, make_response, jsonify, request, abort 2 | from picamera2 import Picamera2 3 | from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageEnhance, ImageChops, ImageFont 4 | from time import sleep, time 5 | import shutil 6 | from OpenScan import load_int, load_float, load_bool, ringlight 7 | import RPi.GPIO as GPIO 8 | from math import sqrt 9 | import os 10 | import math 11 | from skimage import io, feature, color, transform 12 | import numpy as np 13 | from scipy import ndimage 14 | 15 | GPIO.setwarnings(False) 16 | GPIO.setmode(GPIO.BCM) 17 | 18 | app = Flask(__name__) 19 | 20 | basedir = '/home/pi/OpenScan/' 21 | timer = time() 22 | cam_mode = 0 23 | 24 | def overlay_mask(image, mask_image): 25 | # Ensure image is in RGB mode 26 | image_rgb = image.convert('RGB') 27 | # Create an empty image with RGBA channels 28 | overlay = Image.new('RGBA', image_rgb.size) 29 | 30 | # Prepare a red image of the same size 31 | red_image = Image.new('RGB', image_rgb.size, (255, 0, 0)) 32 | # Prepare a mask where the condition is met (mask_image pixels == 255) 33 | mask_condition = np.array(mask_image) > 0 34 | overlay_mask = Image.fromarray(np.uint8(mask_condition) * 255) 35 | # Paste the red image onto the overlay using the condition mask 36 | overlay.paste(red_image, mask=overlay_mask) 37 | # Combine the original image with the overlay 38 | combined = Image.alpha_composite(image_rgb.convert('RGBA'), overlay) 39 | # Convert the final image to RGB 40 | combined_rgb = combined.convert('RGB') 41 | return combined_rgb 42 | 43 | 44 | 45 | def highlight_sharpest_areas(image, threshold=load_int('cam_sharpness'), dilation_size=5): 46 | # Convert PIL image to grayscale 47 | image_gray = image.convert('L') 48 | 49 | # Convert grayscale image to numpy array 50 | image_array = np.array(image_gray) 51 | 52 | # Calculate the gradient using a Sobel filter 53 | dx = ndimage.sobel(image_array, 0) # horizontal derivative 54 | dy = ndimage.sobel(image_array, 1) # vertical derivative 55 | mag = np.hypot(dx, dy) # magnitude 56 | 57 | # Threshold the gradient to create a mask of the sharpest areas 58 | mask = np.where(mag > threshold, 255, 0).astype(np.uint8) 59 | 60 | dilated_mask = ndimage.binary_dilation(mask, structure=np.ones((dilation_size,dilation_size))) 61 | # Create a PIL image from the mask 62 | mask_image = Image.fromarray(dilated_mask) 63 | 64 | return mask_image 65 | 66 | 67 | 68 | 69 | ################################################################################################################### 70 | @app.route('/shutdown', methods=['get']) 71 | def shutdown(): 72 | delay = 0.1 73 | ringlight(2,False) 74 | 75 | for i in range (5): 76 | ringlight(1,True) 77 | sleep(delay) 78 | ringlight(1,False) 79 | sleep(delay) 80 | os.system('shutdown -h now') 81 | ################################################################################################################### 82 | @app.route('/reboot', methods=['get']) 83 | def reboot(): 84 | delay = 0.1 85 | ringlight(2,False) 86 | 87 | for i in range (5): 88 | ringlight(1,True) 89 | sleep(delay) 90 | ringlight(1,False) 91 | sleep(delay) 92 | 93 | os.system('reboot -h') 94 | ################################################################################################################### 95 | 96 | def plot_orb_keypoints(pil_image): 97 | downscale = 2 98 | # Read the image from the given image path 99 | image = np.array(pil_image) 100 | #image = io.imread(image_path) 101 | image = transform.resize(image, (image.shape[0] // downscale, image.shape[1] // downscale), anti_aliasing=True) 102 | 103 | # Convert the image to grayscale 104 | gray_image = color.rgb2gray(image) 105 | 106 | try: 107 | orb = feature.ORB(n_keypoints=10000, downscale=1.2, fast_n=2, fast_threshold=0.2 , n_scales=3, harris_k=0.001) 108 | orb.detect_and_extract(gray_image) 109 | keypoints = orb.keypoints 110 | except: 111 | return pil_image 112 | 113 | # Convert the image back to the range [0, 255] 114 | display_image = (image * 255).astype(np.uint8) 115 | 116 | # Draw the keypoints on the image 117 | draw = ImageDraw.Draw(pil_image) 118 | size = max(2,int(image.shape[0]*downscale*0.005)) 119 | for i, (y, x) in enumerate(keypoints): 120 | draw.ellipse([(downscale*x-size, downscale*y-size), (downscale*x+size, downscale*y+size)], fill = (0,255,0)) 121 | # Save the image with keypoints to the given output path 122 | return pil_image 123 | 124 | def add_histo(img): 125 | histo_size = 241 126 | 127 | img_gray = ImageOps.grayscale(img) 128 | histogram = img_gray.histogram() 129 | histogram_log = [math.log10(h + 1) for h in histogram] 130 | histogram_max = max(histogram_log) 131 | histogram_normalized = [float(h) / histogram_max for h in histogram_log] 132 | hist_image = Image.new("RGBA", (histo_size, histo_size), (255, 255, 255, 0)) 133 | draw = ImageDraw.Draw(hist_image) 134 | 135 | for i in range(0, 256): 136 | x = i 137 | y = 256 - int(histogram_normalized[i] * 256) 138 | draw.line((x, 256, x, y), fill=(0, 0, 0, 255)) 139 | 140 | text = "" 141 | if min(histogram[235:238])>0: 142 | text = "overexposed" 143 | if sum(histogram[190:192])<8: 144 | text = "underexposed" 145 | font = ImageFont.truetype("DejaVuSans.ttf", 30) 146 | 147 | bbox = draw.textbbox((0, 0), text, font=font) 148 | 149 | text_width = bbox[2] - bbox[0] 150 | text_height = bbox[3] - bbox[1] 151 | 152 | 153 | x = (hist_image.width - text_width )/2 154 | y = hist_image.height - text_height - 10 155 | draw.text((x, y), text, font=font, fill=(255,0,0)) 156 | 157 | scale = 0.25 158 | width1, height1 = hist_image.size 159 | width2 = img.size[0] 160 | new_width1 = int(width2 * scale) 161 | new_height1 = int((height1 / width1) * new_width1) 162 | hist_image = hist_image.convert('RGB') 163 | 164 | hist_image = hist_image.resize((new_width1, new_height1)) 165 | x = hist_image.width - text_width - 10 166 | y = hist_image.height - text_height - 10 167 | 168 | 169 | 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]))) 170 | 171 | return img 172 | 173 | def create_mask(image: Image, scale: float = 0.1, threshold: int = 45) -> Image: 174 | threshold = load_int("cam_mask_threshold") 175 | if threshold <= 1: 176 | return image 177 | orig = image 178 | image = image.resize((int(image.width*scale),int(image.height*scale))) 179 | image = image.convert("L") 180 | reduced = image 181 | image = image.filter(ImageFilter.EDGE_ENHANCE) 182 | image = image.filter(ImageFilter.BLUR) 183 | reduced = reduced.filter(ImageFilter.EDGE_ENHANCE_MORE) 184 | mask = ImageChops.difference(image, reduced) 185 | mask = ImageEnhance.Brightness(mask).enhance(2.5) 186 | mask = mask.filter(ImageFilter.MaxFilter(9)) 187 | mask = mask.filter(ImageFilter.MinFilter(5)) 188 | mask = mask.point(lambda x: 255 if x wrong aspect ratio! 211 | # preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}) 212 | preview_config = picam2.create_preview_configuration(main={"size": (2028, 1520)}, controls ={"FrameDurationLimits": (1, 1000000)}) 213 | 214 | # preview_config = picam2.create_preview_configuration(main={"size": (2328, 1748)}) 215 | capture_config = picam2.create_still_configuration(controls ={"FrameDurationLimits": (1, 1000000)}) 216 | picam2.configure(preview_config) 217 | picam2.controls.AnalogueGain = 1.0 218 | picam2.start() 219 | return ({}, 200) 220 | 221 | ################################################################################################################### 222 | @app.route('/picam2_take_photo', methods=['get']) 223 | def picam2_take_photo(): 224 | starttime = time() 225 | 226 | cropx = load_int('cam_cropx')/200 227 | cropy = load_int('cam_cropy')/200 228 | rotation = load_int('cam_rotation') 229 | img = picam2.capture_image() 230 | 231 | if cam_mode !=1: 232 | img = img.convert('RGB') 233 | w,h = img.size 234 | 235 | if cropx != 0 or cropy != 0: 236 | img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) 237 | 238 | if rotation == 90: 239 | img = img.transpose(Image.ROTATE_90) 240 | elif rotation == 180: 241 | img= img.transpose(Image.ROTATE_180) 242 | elif rotation == 270: 243 | img= img.transpose(Image.ROTATE_270) 244 | 245 | if load_bool("cam_mask"): 246 | if cam_mode == 1: 247 | downscale = 0.045*1.4 248 | else: 249 | downscale = 0.1*1.4 250 | img = create_mask(img, downscale) 251 | 252 | if load_bool("cam_features") and not load_bool("cam_sharparea"): 253 | img = plot_orb_keypoints(img) 254 | 255 | if load_bool("cam_sharparea") and not load_bool("cam_features"): 256 | img2 = highlight_sharpest_areas(img) 257 | img = overlay_mask(img, img2) 258 | 259 | if cam_mode != 1 and not load_bool("cam_sharparea") and not load_bool("cam_features"): 260 | img = add_histo(img) 261 | 262 | img.save("/home/pi/OpenScan/tmp2/preview.jpg", quality=load_int('cam_jpeg_quality')) 263 | print("total " + str(int(1000*(time()-starttime))) + "ms") 264 | starttime = time() 265 | 266 | return ({}, 200) 267 | ################################################################################################################### 268 | @app.route('/picam2_focus', methods=['get']) 269 | def picam2_focus(): 270 | focus = float(request.args.get('focus')) 271 | picam2.set_controls({"AfMode": 0, "LensPosition": focus}) 272 | return ({}, 200) 273 | ################################################################################################################### 274 | @app.route('/picam2_af1', methods=['get']) 275 | def picam2_af1(): 276 | from libcamera import controls 277 | 278 | picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0, "AfRange":controls.AfRangeEnum.Macro}) 279 | return ({}, 200) 280 | ################################################################################################################### 281 | @app.route('/picam2_af2', methods=['get']) 282 | def picam2_af2(): 283 | picam2.set_controls({"AfMode": 2 ,"AfTrigger": 0}) 284 | return ({}, 200) 285 | 286 | ################################################################################################################### 287 | @app.route('/picam2_exposure', methods=['get']) 288 | def picam2_exposure(): 289 | exposure = int(request.args.get('exposure')) 290 | picam2.controls.AnalogueGain = 1.0 291 | picam2.controls.ExposureTime = exposure 292 | return ({}, 200) 293 | ################################################################################################################### 294 | @app.route('/picam2_contrast', methods=['get']) 295 | def picam2_contrast(): 296 | contrast = float(request.args.get('contrast')) 297 | picam2.controls.Contrast = contrast 298 | return ({}, 200) 299 | ################################################################################################################### 300 | @app.route('/picam2_saturation', methods=['get']) 301 | def picam2_saturation(): 302 | saturation = float(request.args.get('saturation')) 303 | picam2.controls.Saturation = saturation 304 | return ({}, 200) 305 | ################################################################################################################### 306 | @app.route('/picam2_switch_mode', methods=['get']) 307 | def picam2_switch_mode(): 308 | global cam_mode 309 | cam_mode = int(request.args.get('mode')) 310 | if cam_mode == 1: 311 | picam2.switch_mode(capture_config) 312 | else: 313 | picam2.switch_mode(preview_config) 314 | return ({}, 200) 315 | ################################################################################################################### 316 | @app.route('/picam2_show_mode', methods=['get']) 317 | def picam2_show_mode(): 318 | global cam_mode 319 | return({"mode":cam_mode},200) 320 | ################################################################################################################### 321 | @app.route('/picam2_af', methods=['get']) 322 | def picam2_af(): 323 | picam2.set_controls({"AfMode": 1 ,"AfTrigger": 0}) # --> wait 3-5s 324 | return ({}, 200) 325 | 326 | if __name__ == '__main__': 327 | # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) 328 | app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) 329 | -------------------------------------------------------------------------------- /update/betaArdu/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node-RED Settings created at Thu, 20 Apr 2023 08:41:18 GMT 3 | * 4 | * It can contain any valid JavaScript code that will get run when Node-RED 5 | * is started. 6 | * 7 | * Lines that start with // are commented out. 8 | * Each entry should be separated from the entries above and below by a comma ',' 9 | * 10 | * For more information about individual settings, refer to the documentation: 11 | * https://nodered.org/docs/user-guide/runtime/configuration 12 | * 13 | * The settings are split into the following sections: 14 | * - Flow File and User Directory Settings 15 | * - Security 16 | * - Server Settings 17 | * - Runtime Settings 18 | * - Editor Settings 19 | * - Node Settings 20 | * 21 | **/ 22 | 23 | module.exports = { 24 | 25 | /******************************************************************************* 26 | * Flow File and User Directory Settings 27 | * - flowFile 28 | * - credentialSecret 29 | * - flowFilePretty 30 | * - userDir 31 | * - nodesDir 32 | ******************************************************************************/ 33 | 34 | /** The file containing the flows. If not set, defaults to flows_.json **/ 35 | flowFile: "flows.json", 36 | 37 | /** By default, credentials are encrypted in storage using a generated key. To 38 | * specify your own secret, set the following property. 39 | * If you want to disable encryption of credentials, set this property to false. 40 | * Note: once you set this property, do not change it - doing so will prevent 41 | * node-red from being able to decrypt your existing credentials and they will be 42 | * lost. 43 | */ 44 | credentialSecret: false, 45 | 46 | /** By default, the flow JSON will be formatted over multiple lines making 47 | * it easier to compare changes when using version control. 48 | * To disable pretty-printing of the JSON set the following property to false. 49 | */ 50 | flowFilePretty: true, 51 | 52 | /** By default, all user data is stored in a directory called `.node-red` under 53 | * the user's home directory. To use a different location, the following 54 | * property can be used 55 | */ 56 | //userDir: '/home/nol/.node-red/', 57 | userDir: '/home/pi/OpenScan/settings/.node-red/', 58 | 59 | /** Node-RED scans the `nodes` directory in the userDir to find local node files. 60 | * The following property can be used to specify an additional directory to scan. 61 | */ 62 | //nodesDir: '/home/nol/.node-red/nodes', 63 | 64 | /******************************************************************************* 65 | * Security 66 | * - adminAuth 67 | * - https 68 | * - httpsRefreshInterval 69 | * - requireHttps 70 | * - httpNodeAuth 71 | * - httpStaticAuth 72 | ******************************************************************************/ 73 | 74 | /** To password protect the Node-RED editor and admin API, the following 75 | * property can be used. See http://nodered.org/docs/security.html for details. 76 | */ 77 | //adminAuth: { 78 | // type: "credentials", 79 | // users: [{ 80 | // username: "admin", 81 | // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", 82 | // permissions: "*" 83 | // }] 84 | //}, 85 | 86 | /** The following property can be used to enable HTTPS 87 | * This property can be either an object, containing both a (private) key 88 | * and a (public) certificate, or a function that returns such an object. 89 | * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener 90 | * for details of its contents. 91 | */ 92 | 93 | /** Option 1: static object */ 94 | //https: { 95 | // key: require("fs").readFileSync('privkey.pem'), 96 | // cert: require("fs").readFileSync('cert.pem') 97 | //}, 98 | 99 | /** Option 2: function that returns the HTTP configuration object */ 100 | // https: function() { 101 | // // This function should return the options object, or a Promise 102 | // // that resolves to the options object 103 | // return { 104 | // key: require("fs").readFileSync('privkey.pem'), 105 | // cert: require("fs").readFileSync('cert.pem') 106 | // } 107 | // }, 108 | 109 | /** If the `https` setting is a function, the following setting can be used 110 | * to set how often, in hours, the function will be called. That can be used 111 | * to refresh any certificates. 112 | */ 113 | //httpsRefreshInterval : 12, 114 | 115 | /** The following property can be used to cause insecure HTTP connections to 116 | * be redirected to HTTPS. 117 | */ 118 | //requireHttps: true, 119 | 120 | /** To password protect the node-defined HTTP endpoints (httpNodeRoot), 121 | * including node-red-dashboard, or the static content (httpStatic), the 122 | * following properties can be used. 123 | * The `pass` field is a bcrypt hash of the password. 124 | * See http://nodered.org/docs/security.html#generating-the-password-hash 125 | */ 126 | //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 127 | //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 128 | 129 | /******************************************************************************* 130 | * Server Settings 131 | * - uiPort 132 | * - uiHost 133 | * - apiMaxLength 134 | * - httpServerOptions 135 | * - httpAdminRoot 136 | * - httpAdminMiddleware 137 | * - httpNodeRoot 138 | * - httpNodeCors 139 | * - httpNodeMiddleware 140 | * - httpStatic 141 | * - httpStaticRoot 142 | ******************************************************************************/ 143 | 144 | /** the tcp port that the Node-RED web server is listening on */ 145 | uiPort: process.env.PORT || 80, 146 | 147 | /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. 148 | * To listen on all IPv6 addresses, set uiHost to "::", 149 | * The following property can be used to listen on a specific interface. For 150 | * example, the following would only allow connections from the local machine. 151 | */ 152 | //uiHost: "127.0.0.1", 153 | 154 | /** The maximum size of HTTP request that will be accepted by the runtime api. 155 | * Default: 5mb 156 | */ 157 | //apiMaxLength: '5mb', 158 | 159 | /** The following property can be used to pass custom options to the Express.js 160 | * server used by Node-RED. For a full list of available options, refer 161 | * to http://expressjs.com/en/api.html#app.settings.table 162 | */ 163 | //httpServerOptions: { }, 164 | 165 | /** By default, the Node-RED UI is available at http://localhost:1880/ 166 | * The following property can be used to specify a different root path. 167 | * If set to false, this is disabled. 168 | */ 169 | httpAdminRoot: '/editor', 170 | 171 | /** The following property can be used to add a custom middleware function 172 | * in front of all admin http routes. For example, to set custom http 173 | * headers. It can be a single function or an array of middleware functions. 174 | */ 175 | // httpAdminMiddleware: function(req,res,next) { 176 | // // Set the X-Frame-Options header to limit where the editor 177 | // // can be embedded 178 | // //res.set('X-Frame-Options', 'sameorigin'); 179 | // next(); 180 | // }, 181 | 182 | 183 | /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. 184 | * By default, these are served relative to '/'. The following property 185 | * can be used to specifiy a different root path. If set to false, this is 186 | * disabled. 187 | */ 188 | //httpNodeRoot: '/red-nodes', 189 | 190 | /** The following property can be used to configure cross-origin resource sharing 191 | * in the HTTP nodes. 192 | * See https://github.com/troygoode/node-cors#configuration-options for 193 | * details on its contents. The following is a basic permissive set of options: 194 | */ 195 | //httpNodeCors: { 196 | // origin: "*", 197 | // methods: "GET,PUT,POST,DELETE" 198 | //}, 199 | 200 | /** If you need to set an http proxy please set an environment variable 201 | * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. 202 | * For example - http_proxy=http://myproxy.com:8080 203 | * (Setting it here will have no effect) 204 | * You may also specify no_proxy (or NO_PROXY) to supply a comma separated 205 | * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk 206 | */ 207 | 208 | /** The following property can be used to add a custom middleware function 209 | * in front of all http in nodes. This allows custom authentication to be 210 | * applied to all http in nodes, or any other sort of common request processing. 211 | * It can be a single function or an array of middleware functions. 212 | */ 213 | //httpNodeMiddleware: function(req,res,next) { 214 | // // Handle/reject the request, or pass it on to the http in node by calling next(); 215 | // // Optionally skip our rawBodyParser by setting this to true; 216 | // //req.skipRawBodyParser = true; 217 | // next(); 218 | //}, 219 | 220 | /** When httpAdminRoot is used to move the UI to a different root path, the 221 | * following property can be used to identify a directory of static content 222 | * that should be served at http://localhost:1880/. 223 | * When httpStaticRoot is set differently to httpAdminRoot, there is no need 224 | * to move httpAdminRoot 225 | */ 226 | httpStatic: '/home/pi/OpenScan/', 227 | 228 | //httpStatic: '/home/nol/node-red-static/', //single static source 229 | /* OR multiple static sources can be created using an array of objects... */ 230 | //httpStatic: [ 231 | // {path: '/home/nol/pics/', root: "/img/"}, 232 | // {path: '/home/nol/reports/', root: "/doc/"}, 233 | //], 234 | 235 | /** 236 | * All static routes will be appended to httpStaticRoot 237 | * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" 238 | * then "/home/nol/docs" will be served at "/static/" 239 | * e.g. if httpStatic = [{path: '/home/nol/pics/', root: "/img/"}] 240 | * and httpStaticRoot = "/static/" 241 | * then "/home/nol/pics/" will be served at "/static/img/" 242 | */ 243 | //httpStaticRoot: '/static/', 244 | 245 | /******************************************************************************* 246 | * Runtime Settings 247 | * - lang 248 | * - logging 249 | * - contextStorage 250 | * - exportGlobalContextKeys 251 | * - externalModules 252 | ******************************************************************************/ 253 | 254 | /** Uncomment the following to run node-red in your preferred language. 255 | * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko 256 | * Some languages are more complete than others. 257 | */ 258 | // lang: "de", 259 | 260 | /** Configure the logging output */ 261 | logging: { 262 | /** Only console logging is currently supported */ 263 | console: { 264 | /** Level of logging to be recorded. Options are: 265 | * fatal - only those errors which make the application unusable should be recorded 266 | * error - record errors which are deemed fatal for a particular request + fatal errors 267 | * warn - record problems which are non fatal + errors + fatal errors 268 | * info - record information about the general running of the application + warn + error + fatal errors 269 | * debug - record information which is more verbose than info + info + warn + error + fatal errors 270 | * trace - record very detailed logging + debug + info + warn + error + fatal errors 271 | * off - turn off all logging (doesn't affect metrics or audit) 272 | */ 273 | level: "info", 274 | /** Whether or not to include metric events in the log output */ 275 | metrics: false, 276 | /** Whether or not to include audit events in the log output */ 277 | audit: false 278 | } 279 | }, 280 | 281 | /** Context Storage 282 | * The following property can be used to enable context storage. The configuration 283 | * provided here will enable file-based context that flushes to disk every 30 seconds. 284 | * Refer to the documentation for further options: https://nodered.org/docs/api/context/ 285 | */ 286 | //contextStorage: { 287 | // default: { 288 | // module:"localfilesystem" 289 | // }, 290 | //}, 291 | 292 | /** `global.keys()` returns a list of all properties set in global context. 293 | * This allows them to be displayed in the Context Sidebar within the editor. 294 | * In some circumstances it is not desirable to expose them to the editor. The 295 | * following property can be used to hide any property set in `functionGlobalContext` 296 | * from being list by `global.keys()`. 297 | * By default, the property is set to false to avoid accidental exposure of 298 | * their values. Setting this to true will cause the keys to be listed. 299 | */ 300 | exportGlobalContextKeys: false, 301 | 302 | /** Configure how the runtime will handle external npm modules. 303 | * This covers: 304 | * - whether the editor will allow new node modules to be installed 305 | * - whether nodes, such as the Function node are allowed to have their 306 | * own dynamically configured dependencies. 307 | * The allow/denyList options can be used to limit what modules the runtime 308 | * will install/load. It can use '*' as a wildcard that matches anything. 309 | */ 310 | externalModules: { 311 | // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ 312 | // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ 313 | // palette: { /** Configuration for the Palette Manager */ 314 | // allowInstall: true, /** Enable the Palette Manager in the editor */ 315 | // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ 316 | // allowList: [], 317 | // denyList: [] 318 | // }, 319 | // modules: { /** Configuration for node-specified modules */ 320 | // allowInstall: true, 321 | // allowList: [], 322 | // denyList: [] 323 | // } 324 | }, 325 | 326 | 327 | /******************************************************************************* 328 | * Editor Settings 329 | * - disableEditor 330 | * - editorTheme 331 | ******************************************************************************/ 332 | 333 | /** The following property can be used to disable the editor. The admin API 334 | * is not affected by this option. To disable both the editor and the admin 335 | * API, use either the httpRoot or httpAdminRoot properties 336 | */ 337 | //disableEditor: false, 338 | 339 | /** Customising the editor 340 | * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes 341 | * for all available options. 342 | */ 343 | editorTheme: { 344 | /** The following property can be used to set a custom theme for the editor. 345 | * See https://github.com/node-red-contrib-themes/theme-collection for 346 | * a collection of themes to chose from. 347 | */ 348 | //theme: "", 349 | palette: { 350 | /** The following property can be used to order the categories in the editor 351 | * palette. If a node's category is not in the list, the category will get 352 | * added to the end of the palette. 353 | * If not set, the following default order is used: 354 | */ 355 | //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], 356 | }, 357 | projects: { 358 | /** To enable the Projects feature, set this value to true */ 359 | enabled: false, 360 | workflow: { 361 | /** Set the default projects workflow mode. 362 | * - manual - you must manually commit changes 363 | * - auto - changes are automatically committed 364 | * This can be overridden per-user from the 'Git config' 365 | * section of 'User Settings' within the editor 366 | */ 367 | mode: "manual" 368 | } 369 | }, 370 | codeEditor: { 371 | /** Select the text editor component used by the editor. 372 | * As of Node-RED V3, this defaults to "monaco", but can be set to "ace" if desired 373 | */ 374 | lib: "monaco", 375 | options: { 376 | /** The follow options only apply if the editor is set to "monaco" 377 | * 378 | * theme - must match the file name of a theme in 379 | * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme 380 | * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" 381 | */ 382 | theme: "vs", 383 | /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. 384 | * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html 385 | */ 386 | //fontSize: 14, 387 | //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", 388 | //fontLigatures: true, 389 | } 390 | } 391 | }, 392 | 393 | /******************************************************************************* 394 | * Node Settings 395 | * - fileWorkingDirectory 396 | * - functionGlobalContext 397 | * - functionExternalModules 398 | * - nodeMessageBufferMaxLength 399 | * - ui (for use with Node-RED Dashboard) 400 | * - debugUseColors 401 | * - debugMaxLength 402 | * - execMaxBufferSize 403 | * - httpRequestTimeout 404 | * - mqttReconnectTime 405 | * - serialReconnectTime 406 | * - socketReconnectTime 407 | * - socketTimeout 408 | * - tcpMsgQueueSize 409 | * - inboundWebSocketTimeout 410 | * - tlsConfigDisableLocalFiles 411 | * - webSocketNodeVerifyClient 412 | ******************************************************************************/ 413 | 414 | /** The working directory to handle relative file paths from within the File nodes 415 | * defaults to the working directory of the Node-RED process. 416 | */ 417 | //fileWorkingDirectory: "", 418 | 419 | /** Allow the Function node to load additional npm modules directly */ 420 | functionExternalModules: true, 421 | 422 | /** The following property can be used to set predefined values in Global Context. 423 | * This allows extra node modules to be made available with in Function node. 424 | * For example, the following: 425 | * functionGlobalContext: { os:require('os') } 426 | * will allow the `os` module to be accessed in a Function node using: 427 | * global.get("os") 428 | */ 429 | // functionGlobalContext: { 430 | // os:require('os'), 431 | // }, 432 | functionGlobalContext: { // enables and pre-populates the context.global variable 433 | os:require('os'), 434 | path:require('path'), 435 | fs:require('fs') 436 | }, 437 | /** The maximum number of messages nodes will buffer internally as part of their 438 | * operation. This applies across a range of nodes that operate on message sequences. 439 | * defaults to no limit. A value of 0 also means no limit is applied. 440 | */ 441 | //nodeMessageBufferMaxLength: 0, 442 | 443 | /** If you installed the optional node-red-dashboard you can set it's path 444 | * relative to httpNodeRoot 445 | * Other optional properties include 446 | * readOnly:{boolean}, 447 | * middleware:{function or array}, (req,res,next) - http middleware 448 | * ioMiddleware:{function or array}, (socket,next) - socket.io middleware 449 | */ 450 | ui: { path: "" }, 451 | 452 | /** Colourise the console output of the debug node */ 453 | //debugUseColors: true, 454 | 455 | /** The maximum length, in characters, of any message sent to the debug sidebar tab */ 456 | debugMaxLength: 1000, 457 | 458 | /** Maximum buffer size for the exec node. Defaults to 10Mb */ 459 | //execMaxBufferSize: 10000000, 460 | 461 | /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ 462 | //httpRequestTimeout: 120000, 463 | 464 | /** Retry time in milliseconds for MQTT connections */ 465 | mqttReconnectTime: 15000, 466 | 467 | /** Retry time in milliseconds for Serial port connections */ 468 | serialReconnectTime: 15000, 469 | 470 | /** Retry time in milliseconds for TCP socket connections */ 471 | //socketReconnectTime: 10000, 472 | 473 | /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ 474 | //socketTimeout: 120000, 475 | 476 | /** Maximum number of messages to wait in queue while attempting to connect to TCP socket 477 | * defaults to 1000 478 | */ 479 | //tcpMsgQueueSize: 2000, 480 | 481 | /** Timeout in milliseconds for inbound WebSocket connections that do not 482 | * match any configured node. Defaults to 5000 483 | */ 484 | //inboundWebSocketTimeout: 5000, 485 | 486 | /** To disable the option for using local files for storing keys and 487 | * certificates in the TLS configuration node, set this to true. 488 | */ 489 | //tlsConfigDisableLocalFiles: true, 490 | 491 | /** The following property can be used to verify websocket connection attempts. 492 | * This allows, for example, the HTTP request headers to be checked to ensure 493 | * they include valid authentication information. 494 | */ 495 | //webSocketNodeVerifyClient: function(info) { 496 | // /** 'info' has three properties: 497 | // * - origin : the value in the Origin header 498 | // * - req : the HTTP request 499 | // * - secure : true if req.connection.authorized or req.connection.encrypted is set 500 | // * 501 | // * The function should return true if the connection should be accepted, false otherwise. 502 | // * 503 | // * Alternatively, if this function is defined to accept a second argument, callback, 504 | // * it can be used to verify the client asynchronously. 505 | // * The callback takes three arguments: 506 | // * - result : boolean, whether to accept the connection or not 507 | // * - code : if result is false, the HTTP error status to return 508 | // * - reason: if result is false, the HTTP reason string to return 509 | // */ 510 | //}, 511 | } 512 | -------------------------------------------------------------------------------- /update/files/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenScan-org/OpenScan2/373635007c782daf241f0d344d48acb0388dfc41/update/files/logo.jpg -------------------------------------------------------------------------------- /update/files/logo2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenScan-org/OpenScan2/373635007c782daf241f0d344d48acb0388dfc41/update/files/logo2.jpg -------------------------------------------------------------------------------- /update/main/Arducam.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | 4 | try: 5 | import v4l2 6 | except Exception as e: 7 | print(e) 8 | print("Try to install v4l2-fix") 9 | try: 10 | from pip import main as pipmain 11 | except ImportError: 12 | from pip._internal import main as pipmain 13 | pipmain(['install', 'v4l2-fix']) 14 | print("\nTry to run the focus program again.") 15 | exit(0) 16 | 17 | import fcntl 18 | import errno 19 | 20 | # # Type 21 | # v4l2.V4L2_CTRL_TYPE_INTEGER 22 | # v4l2.V4L2_CTRL_TYPE_BOOLEAN 23 | # v4l2.V4L2_CTRL_TYPE_MENU 24 | # v4l2.V4L2_CTRL_TYPE_BUTTON 25 | # v4l2.V4L2_CTRL_TYPE_INTEGER64 26 | # v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 27 | # # Flags 28 | # v4l2.V4L2_CTRL_FLAG_DISABLED 29 | # v4l2.V4L2_CTRL_FLAG_GRABBED 30 | # v4l2.V4L2_CTRL_FLAG_READ_ONLY 31 | # v4l2.V4L2_CTRL_FLAG_UPDATE 32 | # v4l2.V4L2_CTRL_FLAG_INACTIVE 33 | # v4l2.V4L2_CTRL_FLAG_SLIDER 34 | 35 | def assert_valid_queryctrl(queryctrl): 36 | return queryctrl.type & ( 37 | v4l2.V4L2_CTRL_TYPE_INTEGER 38 | | v4l2.V4L2_CTRL_TYPE_BOOLEAN 39 | | v4l2.V4L2_CTRL_TYPE_MENU 40 | | v4l2.V4L2_CTRL_TYPE_BUTTON 41 | | v4l2.V4L2_CTRL_TYPE_INTEGER64 42 | | v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 43 | | 7 44 | | 8 45 | | 9 46 | ) and queryctrl.flags & ( 47 | v4l2.V4L2_CTRL_FLAG_DISABLED 48 | | v4l2.V4L2_CTRL_FLAG_GRABBED 49 | | v4l2.V4L2_CTRL_FLAG_READ_ONLY 50 | | v4l2.V4L2_CTRL_FLAG_UPDATE 51 | | v4l2.V4L2_CTRL_FLAG_INACTIVE 52 | | v4l2.V4L2_CTRL_FLAG_SLIDER 53 | ) 54 | 55 | def get_device_controls_menu(fd, queryctrl): 56 | querymenu = v4l2.v4l2_querymenu(queryctrl.id, queryctrl.minimum) 57 | while querymenu.index <= queryctrl.maximum: 58 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYMENU, querymenu) 59 | yield querymenu 60 | querymenu.index += 1 61 | 62 | def get_device_controls_by_class(fd, control_class): 63 | # enumeration by control class 64 | queryctrl = v4l2.v4l2_queryctrl(control_class | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 65 | while True: 66 | try: 67 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 68 | except IOError as e: 69 | assert e.errno == errno.EINVAL 70 | break 71 | if v4l2.V4L2_CTRL_ID2CLASS(queryctrl.id) != control_class: 72 | break 73 | yield queryctrl 74 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 75 | 76 | def getdict(struct): 77 | val = dict((field, getattr(struct, field)) for field, _ in struct._fields_) 78 | val.pop("reserved") 79 | return val 80 | 81 | def get_device_controls(fd): 82 | # original enumeration method 83 | queryctrl = v4l2.v4l2_queryctrl(v4l2.V4L2_CID_BASE) 84 | while queryctrl.id < v4l2.V4L2_CID_LASTP1: 85 | try: 86 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 87 | print(queryctrl.name) 88 | except IOError as e: 89 | # this predefined control is not supported by this device 90 | assert e.errno == errno.EINVAL 91 | queryctrl.id += 1 92 | continue 93 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id + 1) 94 | 95 | def get_ctrls(vd): 96 | ctrls = [] 97 | # enumeration by control class 98 | for class_ in (v4l2.V4L2_CTRL_CLASS_USER, v4l2.V4L2_CTRL_CLASS_MPEG, v4l2.V4L2_CTRL_CLASS_CAMERA): 99 | for queryctrl in get_device_controls_by_class(vd, class_): 100 | ctrl = getdict(queryctrl) 101 | if queryctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: 102 | ctrl["menu"] = [] 103 | for querymenu in get_device_controls_menu(vd, queryctrl): 104 | # print(querymenu.name) 105 | ctrl["menu"].append(querymenu.name) 106 | 107 | if queryctrl.type == 9: 108 | ctrl["menu"] = [] 109 | for querymenu in get_device_controls_menu(vd, queryctrl): 110 | ctrl["menu"].append(querymenu.index) 111 | ctrls.append(ctrl) 112 | return ctrls 113 | 114 | def set_ctrl(vd, id, value): 115 | ctrl = v4l2.v4l2_control() 116 | ctrl.id = id 117 | ctrl.value = value 118 | try: 119 | fcntl.ioctl(vd, v4l2.VIDIOC_S_CTRL, ctrl) 120 | except IOError as e: 121 | print(e) 122 | 123 | def get_ctrl(vd, id): 124 | ctrl = v4l2.v4l2_control() 125 | ctrl.id = id 126 | try: 127 | fcntl.ioctl(vd, v4l2.VIDIOC_G_CTRL, ctrl) 128 | except IOError as e: 129 | print(e) 130 | return None 131 | return ctrl.value 132 | 133 | 134 | class Focuser: 135 | FOCUS_ID = 0x009a090a 136 | dev = None 137 | 138 | def __init__(self, dev=0): 139 | self.focus_value = 0 140 | self.dev = dev 141 | 142 | if type(dev) == int or (type(dev) == str and dev.isnumeric()): 143 | self.dev = "/dev/video{}".format(dev) 144 | 145 | self.fd = open(self.dev, 'r') 146 | self.ctrls = get_ctrls(self.fd) 147 | self.hasFocus = False 148 | for ctrl in self.ctrls: 149 | if ctrl['id'] == Focuser.FOCUS_ID: 150 | self.hasFocus = True 151 | self.opts[Focuser.OPT_FOCUS]["MIN_VALUE"] = ctrl['minimum'] 152 | self.opts[Focuser.OPT_FOCUS]["MAX_VALUE"] = ctrl['maximum'] 153 | self.opts[Focuser.OPT_FOCUS]["DEF_VALUE"] = ctrl['default'] 154 | self.focus_value = get_ctrl(self.fd, Focuser.FOCUS_ID) 155 | 156 | if not self.hasFocus: 157 | raise RuntimeError("Device {} has no focus_absolute control.".format(self.dev)) 158 | 159 | def read(self): 160 | return self.focus_value 161 | 162 | def write(self, value): 163 | self.focus_value = value 164 | # os.system("v4l2-ctl -d {} -c focus_absolute={}".format(self.dev, value)) 165 | set_ctrl(self.fd, Focuser.FOCUS_ID, value) 166 | 167 | OPT_BASE = 0x1000 168 | OPT_FOCUS = OPT_BASE | 0x01 169 | OPT_ZOOM = OPT_BASE | 0x02 170 | OPT_MOTOR_X = OPT_BASE | 0x03 171 | OPT_MOTOR_Y = OPT_BASE | 0x04 172 | OPT_IRCUT = OPT_BASE | 0x05 173 | opts = { 174 | OPT_FOCUS : { 175 | "MIN_VALUE": 0, 176 | "MAX_VALUE": 1000, 177 | "DEF_VALUE": 0, 178 | }, 179 | } 180 | def reset(self,opt,flag = 1): 181 | info = self.opts[opt] 182 | if info == None or info["DEF_VALUE"] == None: 183 | return 184 | self.set(opt,info["DEF_VALUE"]) 185 | 186 | def get(self,opt,flag = 0): 187 | info = self.opts[opt] 188 | return self.read() 189 | 190 | def set(self,opt,value,flag = 1): 191 | info = self.opts[opt] 192 | if value > info["MAX_VALUE"]: 193 | value = info["MAX_VALUE"] 194 | elif value < info["MIN_VALUE"]: 195 | value = info["MIN_VALUE"] 196 | self.write(value) 197 | print("write: {}".format(value)) 198 | 199 | def __del__(self): 200 | self.fd.close() 201 | 202 | pass 203 | -------------------------------------------------------------------------------- /update/main/OpenScan.py: -------------------------------------------------------------------------------- 1 | basepath = '/home/pi/OpenScan/' 2 | from os.path import isfile 3 | 4 | def load_bool(name): 5 | filename = basepath+'settings/'+name 6 | if not isfile(filename): 7 | return 8 | with open(filename, 'r') as file: 9 | value = file.read().replace('\n','') 10 | if value == '1' or value == 'True' or value =='true': 11 | value = True 12 | else: 13 | value = False 14 | return value 15 | 16 | def load_str(name): 17 | filename = basepath+'settings/'+name 18 | if not isfile(filename): 19 | return 20 | with open(filename, 'r') as file: 21 | value = file.read().replace('\n','') 22 | return value 23 | 24 | def load_int(name): 25 | filename = basepath+'settings/'+name 26 | if not isfile(filename): 27 | return 28 | with open(filename, 'r') as file: 29 | value = int(file.read().replace('\n','')) 30 | return value 31 | 32 | def load_float(name): 33 | filename = basepath+'settings/'+name 34 | if not isfile(filename): 35 | return 36 | with open(filename, 'r') as file: 37 | value = float(file.read().replace('\n','')) 38 | return value 39 | 40 | def save(name, value): 41 | filename = basepath+'settings/'+name 42 | with open(filename, 'w+') as file: 43 | file.write(str(value)) 44 | return 45 | 46 | def OpenScanCloud(cmd, msg): 47 | from requests import get 48 | osc_user = 'openscan' 49 | osc_pw = 'free' 50 | osc_server = 'http://openscanfeedback.dnsuser.de:1334/' 51 | 52 | try: 53 | r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) 54 | except: 55 | r = type('obj', (object,), {'status_code' : 404, 'text':None}) 56 | return r 57 | 58 | def camera(cmd, msg = {}): 59 | from requests import get 60 | flask = 'http://127.0.0.1:1312/' 61 | try: 62 | r = get(flask + cmd, params=msg) 63 | return r.status_code 64 | except: 65 | return 400 66 | 67 | def motorrun(motor,angle): 68 | import RPi.GPIO as GPIO 69 | from time import sleep 70 | from math import cos 71 | msg = {'cmd':'set'} 72 | camera('/ping', msg) 73 | 74 | GPIO.setwarnings(False) 75 | GPIO.setmode(GPIO.BCM) 76 | 77 | spr = load_int(motor + '_stepsperrotation') 78 | dirpin = load_int('pin_' + motor + '_dir') 79 | steppin = load_int('pin_' + motor +'_step') 80 | dir = load_int(motor + '_dir') 81 | ramp = load_int(motor + '_accramp') 82 | acc = load_float(motor + '_acc') 83 | delay_init = load_float(motor + '_delay') 84 | delay = delay_init 85 | 86 | step_count=int(angle*spr/360) * dir 87 | GPIO.setup(dirpin, GPIO.OUT) 88 | GPIO.setup(steppin, GPIO.OUT) 89 | if (step_count>0): 90 | GPIO.output(dirpin, GPIO.HIGH) 91 | if(step_count<0): 92 | GPIO.output(dirpin, GPIO.LOW) 93 | step_count=-step_count 94 | for x in range(step_count): 95 | GPIO.output(steppin, GPIO.HIGH) 96 | if x<=ramp and x<=step_count/2: 97 | delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) 98 | #delay=delay_init+(ramp-x)*(delay_init)/acc 99 | elif step_count-x<=ramp and x>step_count/2: 100 | delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) 101 | #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc 102 | else: 103 | delay = delay_init 104 | sleep(delay) 105 | GPIO.output(steppin, GPIO.LOW) 106 | sleep(delay) 107 | 108 | def ringlight(number,state): 109 | import RPi.GPIO as GPIO 110 | msg = {'cmd':'set'} 111 | camera('/ping', msg) 112 | pin = load_int('pin_ringlight' + str(number)) 113 | GPIO.setwarnings(False) 114 | GPIO.setmode(GPIO.BCM) 115 | GPIO.setup(pin, GPIO.OUT) 116 | GPIO.output(pin, state) 117 | 118 | def take_photo(file): 119 | from os import system 120 | filepath = basepath + file 121 | 122 | model=load_str('model') 123 | 124 | 125 | 126 | shutter = str(load_int('cam_shutter')) 127 | saturation = load_str('cam_saturation') 128 | contrast = load_str('cam_contrast') 129 | awbg_red = load_str('cam_awbg_red') 130 | awbg_blue = load_str('cam_awbg_blue') 131 | gain = load_str('cam_gain') 132 | quality = load_int('cam_jpeg_quality') 133 | filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' 134 | #width = load_str('cam_resx') 135 | #height = load_str('cam_resy') 136 | timeout = load_str('cam_timeout') 137 | cropx = load_int('cam_cropx')/200 138 | cropy = load_int('cam_cropy')/200 139 | rotation = load_int('cam_rotation') 140 | AF = load_bool('cam_AFmode') 141 | camera = load_str('camera') 142 | 143 | 144 | if camera == 'imx519' and AF == True: 145 | autofocus = ' --autofocus ' 146 | else: 147 | autofocus = '' 148 | 149 | if camera == "usb_webcam": 150 | cmd = 'fswebcam -i 0 -r "1280x720" -F 5 --no-banner --jpeg 95 --save ' + filepath2 151 | else: 152 | 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' 153 | # 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 154 | 155 | system(cmd) 156 | return cmd 157 | 158 | def get_points(samples=1): 159 | from math import pi, sqrt, acos, atan2, cos, sin 160 | 161 | points = [] 162 | phi = pi * (3. - sqrt(5.)) 163 | for i in range(int(samples)): 164 | y = 1 - (i / float(samples - 1)) * 2 165 | radius = sqrt(1 - y * y) 166 | theta = phi * i 167 | x = cos(theta) * radius 168 | z = sin(theta) * radius 169 | r=sqrt(x*x+y*y+z*z) 170 | theta_neu=acos(z/r)*180/pi 171 | phi_neu=atan2(y,x)*180/pi 172 | points.append((theta_neu-90,phi_neu)) 173 | points.sort() 174 | return points 175 | 176 | def create_coordinates(angle_min, angle_max,point_count): 177 | point_count_final=point_count 178 | if angle_max < angle_min: 179 | a = angle_min 180 | angle_min = angle_max 181 | angle_max = a 182 | point_count=point_count*90/(angle_max-angle_min) 183 | actual_points=0 184 | while actual_pointsangle_min and x20: 193 | point_count=point_count+3 194 | else: 195 | point_count=point_count+1 196 | return filtered 197 | 198 | -------------------------------------------------------------------------------- /update/main/config.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rpf.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | 6 | # uncomment if you get no picture on HDMI for a default "safe" mode 7 | #hdmi_safe=1 8 | hdmi_blanking=2 9 | 10 | # uncomment the following to adjust overscan. Use positive numbers if console 11 | # goes off screen, and negative if there is too much border 12 | #overscan_left=16 13 | #overscan_right=16 14 | #overscan_top=16 15 | #overscan_bottom=16 16 | 17 | # uncomment to force a console size. By default it will be display's size minus 18 | # overscan. 19 | #framebuffer_width=1280 20 | #framebuffer_height=720 21 | 22 | # uncomment if hdmi display is not detected and composite is being output 23 | #hdmi_force_hotplug=1 24 | 25 | # uncomment to force a specific HDMI mode (this will force VGA) 26 | #hdmi_group=1 27 | #hdmi_mode=1 28 | 29 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 30 | # DMT (computer monitor) modes 31 | #hdmi_drive=2 32 | 33 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 34 | # no display 35 | #config_hdmi_boost=4 36 | 37 | # uncomment for composite PAL 38 | #sdtv_mode=2 39 | 40 | #uncomment to overclock the arm. 700 MHz is the default. 41 | #arm_freq=800 42 | 43 | # Uncomment some or all of these to enable the optional hardware interfaces 44 | #dtparam=i2c_arm=on 45 | #dtparam=i2s=on 46 | #dtparam=spi=on 47 | 48 | # Uncomment this to enable infrared communication. 49 | #dtoverlay=gpio-ir,gpio_pin=17 50 | #dtoverlay=gpio-ir-tx,gpio_pin=18 51 | 52 | # Additional overlays and parameters are documented /boot/overlays/README 53 | 54 | # Enable audio (loads snd_bcm2835) 55 | dtparam=audio=on 56 | 57 | # Automatically load overlays for detected cameras 58 | camera_auto_detect=0 59 | 60 | # Automatically load overlays for detected DSI displays 61 | display_auto_detect=1 62 | 63 | # Enable DRM VC4 V3D driver 64 | #dtoverlay=vc4-kms-v3d 65 | max_framebuffers=2 66 | 67 | # Disable compensation for displays with overscan 68 | disable_overscan=1 69 | 70 | [cm4] 71 | # Enable host mode on the 2711 built-in XHCI USB controller. 72 | # This line should be removed if the legacy DWC2 controller is required 73 | # (e.g. for USB device mode) or if USB support is not required. 74 | otg_mode=1 75 | 76 | [pi4] 77 | # Run as fast as firmware / board allows 78 | arm_boost=1 79 | 80 | [all] 81 | 82 | camera_auto_detect=0 83 | gpu_mem=256 84 | dtoverlay=vc4-fkms-v3d 85 | dtoverlay=imx519,media-controller=1 86 | -------------------------------------------------------------------------------- /update/main/fla.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, make_response, jsonify, request, abort 2 | from PIL import Image 3 | import gphoto2 as gp 4 | from time import sleep, time 5 | import shutil 6 | from OpenScan import load_int, load_str, load_float, load_bool, ringlight 7 | import RPi.GPIO as GPIO 8 | from math import sqrt 9 | import os 10 | 11 | GPIO.setwarnings(False) 12 | GPIO.setmode(GPIO.BCM) 13 | 14 | app = Flask(__name__) 15 | 16 | basedir = '/home/pi/OpenScan/' 17 | timer = time() 18 | 19 | 20 | ################################################################################################################### 21 | @app.route('/shutdown', methods=['get']) 22 | def shutdown(): 23 | delay = 0.2 24 | ringlight(2, False) 25 | for i in range(5): 26 | ringlight(1, True) 27 | sleep(delay) 28 | ringlight(1, False) 29 | sleep(delay) 30 | os.system('shutdown -h now') 31 | return "Shutting down...", 200 32 | ################################################################################################################### 33 | @app.route('/reboot', methods=['get']) 34 | def reboot(): 35 | delay = 0.2 36 | ringlight(2, False) 37 | 38 | for i in range(5): 39 | ringlight(1, True) 40 | sleep(delay) 41 | ringlight(1, False) 42 | sleep(delay) 43 | os.system('reboot -h') 44 | return "Rebooting...", 200 45 | ################################################################################################################### 46 | @app.route('/ping', methods=['get']) 47 | def ping(): 48 | global timer 49 | cmd = str(request.args.get('cmd')) 50 | if cmd == 'set': 51 | timer = time() 52 | inactive = time() - timer 53 | return ({'inactive':inactive}, 200) 54 | ################################################################################################################### 55 | @app.route('/gphoto_init', methods=['get']) 56 | def gphoto_init(): 57 | global camera 58 | camera = gp.Camera() 59 | camera.init() 60 | return ({}, 200) 61 | ################################################################################################################### 62 | @app.route('/gphoto_preview', methods=['get']) 63 | def gphoto_preview(): 64 | filepath = str(request.args.get('filepath')) 65 | camera_file = gp.gp_camera_capture_preview(camera)[1] 66 | target = basedir + filepath 67 | camera_file.save(target) 68 | return ({}, 200) 69 | ################################################################################################################### 70 | @app.route('/gphoto_capture', methods=['get']) 71 | def gphoto_capture(): 72 | filepath = str(request.args.get('filepath')) 73 | file_path = camera.capture(gp.GP_CAPTURE_IMAGE) 74 | camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) 75 | camera_file.save(basedir + filepath) 76 | return ({}, 200) 77 | ################################################################################################################### 78 | @app.route('/gphoto_test', methods=['get']) 79 | def gphoto_test(): 80 | text = camera.get_summary() 81 | return ({}, 200) 82 | ################################################################################################################### 83 | @app.route('/gphoto_exit', methods=['get']) 84 | def gphoto_exit(): 85 | global camera 86 | camera.exit() 87 | return ({}, 200) 88 | ################################################################################################################### 89 | @app.route('/crop', methods=['get']) 90 | def crop(): 91 | output_downscale = load_bool('cam_output_downscale') 92 | output_resolution = load_int('cam_output_resolution') 93 | preview_resolution = load_int('cam_preview_resolution') 94 | filepath_in = basedir + str(request.args.get('filepath_in')) 95 | filepath_out = basedir + str(request.args.get('filepath_out')) 96 | cropx = int(request.args.get('cropx'))/200 97 | cropy = int(request.args.get('cropy'))/200 98 | rotation = int(request.args.get('rotation')) 99 | preview = str(request.args.get('preview')) 100 | downscale = 1 101 | 102 | with Image.open(filepath_in) as img: 103 | w,h = img.size 104 | if cropx != 0 or cropy != 0: 105 | img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) 106 | if rotation == 90: 107 | img = img.transpose(Image.ROTATE_90) 108 | elif rotation == 180: 109 | img= img.transpose(Image.ROTATE_180) 110 | elif rotation == 270: 111 | img= img.transpose(Image.ROTATE_270) 112 | 113 | if preview == "True": 114 | w,h = img.size 115 | factor = (w*h)/preview_resolution 116 | if factor > 1: 117 | img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) 118 | 119 | elif output_downscale == True: 120 | w,h = img.size 121 | factor = (w*h)/output_resolution 122 | if factor > 1: 123 | img = img.resize((int(w/sqrt(factor)),int(h/sqrt(factor))),Image.ANTIALIAS) 124 | 125 | img.save(filepath_out, quality=95, subsampling=0) 126 | 127 | return ({}, 200) 128 | 129 | ################################################################################################################### 130 | @app.route('/external_capture', methods=['get']) 131 | def external_capture(): 132 | pin = load_int('pin_external') 133 | delay_before = load_float('cam_delay_before') 134 | timeout = load_float('cam_timeout')/1000 135 | delay_after = load_float('cam_delay_after') 136 | GPIO.setup(pin, GPIO.OUT) 137 | GPIO.output(pin, GPIO.LOW) 138 | sleep(delay_before) 139 | GPIO.output(pin, GPIO.HIGH) 140 | sleep(timeout) 141 | GPIO.output(pin, GPIO.LOW) 142 | sleep(delay_after) 143 | return ({}, 200) 144 | 145 | 146 | 147 | 148 | if __name__ == '__main__': 149 | # app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) 150 | app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) 151 | -------------------------------------------------------------------------------- /update/main/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node-RED Settings created at Mon, 24 Jan 2022 08:17:31 GMT 3 | * 4 | * It can contain any valid JavaScript code that will get run when Node-RED 5 | * is started. 6 | * 7 | * Lines that start with // are commented out. 8 | * Each entry should be separated from the entries above and below by a comma ',' 9 | * 10 | * For more information about individual settings, refer to the documentation: 11 | * https://nodered.org/docs/user-guide/runtime/configuration 12 | * 13 | * The settings are split into the following sections: 14 | * - Flow File and User Directory Settings 15 | * - Security 16 | * - Server Settings 17 | * - Runtime Settings 18 | * - Editor Settings 19 | * - Node Settings 20 | * 21 | **/ 22 | 23 | module.exports = { 24 | 25 | /******************************************************************************* 26 | * Flow File and User Directory Settings 27 | * - flowFile 28 | * - credentialSecret 29 | * - flowFilePretty 30 | * - userDir 31 | * - nodesDir 32 | ******************************************************************************/ 33 | 34 | /** The file containing the flows. If not set, defaults to flows_.json **/ 35 | flowFile: "flows.json", 36 | 37 | /** By default, credentials are encrypted in storage using a generated key. To 38 | * specify your own secret, set the following property. 39 | * If you want to disable encryption of credentials, set this property to false. 40 | * Note: once you set this property, do not change it - doing so will prevent 41 | * node-red from being able to decrypt your existing credentials and they will be 42 | * lost. 43 | */ 44 | credentialSecret: false, 45 | 46 | /** By default, the flow JSON will be formatted over multiple lines making 47 | * it easier to compare changes when using version control. 48 | * To disable pretty-printing of the JSON set the following property to false. 49 | */ 50 | flowFilePretty: true, 51 | 52 | /** By default, all user data is stored in a directory called `.node-red` under 53 | * the user's home directory. To use a different location, the following 54 | * property can be used 55 | */ 56 | //userDir: '/home/nol/.node-red/', 57 | userDir: '/home/pi/OpenScan/settings/.node-red/', 58 | /** Node-RED scans the `nodes` directory in the userDir to find local node files. 59 | * The following property can be used to specify an additional directory to scan. 60 | */ 61 | //nodesDir: '/home/nol/.node-red/nodes', 62 | 63 | /******************************************************************************* 64 | * Security 65 | * - adminAuth 66 | * - https 67 | * - httpsRefreshInterval 68 | * - requireHttps 69 | * - httpNodeAuth 70 | * - httpStaticAuth 71 | ******************************************************************************/ 72 | 73 | /** To password protect the Node-RED editor and admin API, the following 74 | * property can be used. See http://nodered.org/docs/security.html for details. 75 | */ 76 | //adminAuth: { 77 | // type: "credentials", 78 | // users: [{ 79 | // username: "admin", 80 | // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", 81 | // permissions: "*" 82 | // }] 83 | //}, 84 | 85 | /** The following property can be used to enable HTTPS 86 | * This property can be either an object, containing both a (private) key 87 | * and a (public) certificate, or a function that returns such an object. 88 | * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener 89 | * for details of its contents. 90 | */ 91 | 92 | /** Option 1: static object */ 93 | //https: { 94 | // key: require("fs").readFileSync('privkey.pem'), 95 | // cert: require("fs").readFileSync('cert.pem') 96 | //}, 97 | 98 | /** Option 2: function that returns the HTTP configuration object */ 99 | // https: function() { 100 | // // This function should return the options object, or a Promise 101 | // // that resolves to the options object 102 | // return { 103 | // key: require("fs").readFileSync('privkey.pem'), 104 | // cert: require("fs").readFileSync('cert.pem') 105 | // } 106 | // }, 107 | 108 | /** If the `https` setting is a function, the following setting can be used 109 | * to set how often, in hours, the function will be called. That can be used 110 | * to refresh any certificates. 111 | */ 112 | //httpsRefreshInterval : 12, 113 | 114 | /** The following property can be used to cause insecure HTTP connections to 115 | * be redirected to HTTPS. 116 | */ 117 | //requireHttps: true, 118 | 119 | /** To password protect the node-defined HTTP endpoints (httpNodeRoot), 120 | * including node-red-dashboard, or the static content (httpStatic), the 121 | * following properties can be used. 122 | * The `pass` field is a bcrypt hash of the password. 123 | * See http://nodered.org/docs/security.html#generating-the-password-hash 124 | */ 125 | //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 126 | //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 127 | 128 | /******************************************************************************* 129 | * Server Settings 130 | * - uiPort 131 | * - uiHost 132 | * - apiMaxLength 133 | * - httpServerOptions 134 | * - httpAdminRoot 135 | * - httpAdminMiddleware 136 | * - httpNodeRoot 137 | * - httpNodeCors 138 | * - httpNodeMiddleware 139 | * - httpStatic 140 | ******************************************************************************/ 141 | 142 | /** the tcp port that the Node-RED web server is listening on */ 143 | // uiPort: process.env.PORT || 1880, 144 | uiPort: process.env.PORT || 80, 145 | /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. 146 | * To listen on all IPv6 addresses, set uiHost to "::", 147 | * The following property can be used to listen on a specific interface. For 148 | * example, the following would only allow connections from the local machine. 149 | */ 150 | //uiHost: "127.0.0.1", 151 | 152 | /** The maximum size of HTTP request that will be accepted by the runtime api. 153 | * Default: 5mb 154 | */ 155 | //apiMaxLength: '5mb', 156 | 157 | /** The following property can be used to pass custom options to the Express.js 158 | * server used by Node-RED. For a full list of available options, refer 159 | * to http://expressjs.com/en/api.html#app.settings.table 160 | */ 161 | //httpServerOptions: { }, 162 | 163 | /** By default, the Node-RED UI is available at http://localhost:1880/ 164 | * The following property can be used to specify a different root path. 165 | * If set to false, this is disabled. 166 | */ 167 | //httpAdminRoot: '/admin', 168 | httpAdminRoot: '/editor', 169 | /** The following property can be used to add a custom middleware function 170 | * in front of all admin http routes. For example, to set custom http 171 | * headers. It can be a single function or an array of middleware functions. 172 | */ 173 | // httpAdminMiddleware: function(req,res,next) { 174 | // // Set the X-Frame-Options header to limit where the editor 175 | // // can be embedded 176 | // //res.set('X-Frame-Options', 'sameorigin'); 177 | // next(); 178 | // }, 179 | 180 | 181 | /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. 182 | * By default, these are served relative to '/'. The following property 183 | * can be used to specifiy a different root path. If set to false, this is 184 | * disabled. 185 | */ 186 | //httpNodeRoot: '/red-nodes', 187 | 188 | /** The following property can be used to configure cross-origin resource sharing 189 | * in the HTTP nodes. 190 | * See https://github.com/troygoode/node-cors#configuration-options for 191 | * details on its contents. The following is a basic permissive set of options: 192 | */ 193 | //httpNodeCors: { 194 | // origin: "*", 195 | // methods: "GET,PUT,POST,DELETE" 196 | //}, 197 | 198 | /** If you need to set an http proxy please set an environment variable 199 | * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. 200 | * For example - http_proxy=http://myproxy.com:8080 201 | * (Setting it here will have no effect) 202 | * You may also specify no_proxy (or NO_PROXY) to supply a comma separated 203 | * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk 204 | */ 205 | 206 | /** The following property can be used to add a custom middleware function 207 | * in front of all http in nodes. This allows custom authentication to be 208 | * applied to all http in nodes, or any other sort of common request processing. 209 | * It can be a single function or an array of middleware functions. 210 | */ 211 | //httpNodeMiddleware: function(req,res,next) { 212 | // // Handle/reject the request, or pass it on to the http in node by calling next(); 213 | // // Optionally skip our rawBodyParser by setting this to true; 214 | // //req.skipRawBodyParser = true; 215 | // next(); 216 | //}, 217 | 218 | /** When httpAdminRoot is used to move the UI to a different root path, the 219 | * following property can be used to identify a directory of static content 220 | * that should be served at http://localhost:1880/. 221 | */ 222 | //httpStatic: '/home/nol/node-red-static/', 223 | httpStatic: '/home/pi/OpenScan/', 224 | /******************************************************************************* 225 | * Runtime Settings 226 | * - lang 227 | * - logging 228 | * - contextStorage 229 | * - exportGlobalContextKeys 230 | * - externalModules 231 | ******************************************************************************/ 232 | 233 | /** Uncomment the following to run node-red in your preferred language. 234 | * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko 235 | * Some languages are more complete than others. 236 | */ 237 | // lang: "de", 238 | 239 | /** Configure the logging output */ 240 | logging: { 241 | /** Only console logging is currently supported */ 242 | console: { 243 | /** Level of logging to be recorded. Options are: 244 | * fatal - only those errors which make the application unusable should be recorded 245 | * error - record errors which are deemed fatal for a particular request + fatal errors 246 | * warn - record problems which are non fatal + errors + fatal errors 247 | * info - record information about the general running of the application + warn + error + fatal errors 248 | * debug - record information which is more verbose than info + info + warn + error + fatal errors 249 | * trace - record very detailed logging + debug + info + warn + error + fatal errors 250 | * off - turn off all logging (doesn't affect metrics or audit) 251 | */ 252 | level: "info", 253 | /** Whether or not to include metric events in the log output */ 254 | metrics: false, 255 | /** Whether or not to include audit events in the log output */ 256 | audit: false 257 | } 258 | }, 259 | 260 | /** Context Storage 261 | * The following property can be used to enable context storage. The configuration 262 | * provided here will enable file-based context that flushes to disk every 30 seconds. 263 | * Refer to the documentation for further options: https://nodered.org/docs/api/context/ 264 | */ 265 | //contextStorage: { 266 | // default: { 267 | // module:"localfilesystem" 268 | // }, 269 | //}, 270 | 271 | /** `global.keys()` returns a list of all properties set in global context. 272 | * This allows them to be displayed in the Context Sidebar within the editor. 273 | * In some circumstances it is not desirable to expose them to the editor. The 274 | * following property can be used to hide any property set in `functionGlobalContext` 275 | * from being list by `global.keys()`. 276 | * By default, the property is set to false to avoid accidental exposure of 277 | * their values. Setting this to true will cause the keys to be listed. 278 | */ 279 | exportGlobalContextKeys: false, 280 | 281 | /** Configure how the runtime will handle external npm modules. 282 | * This covers: 283 | * - whether the editor will allow new node modules to be installed 284 | * - whether nodes, such as the Function node are allowed to have their 285 | * own dynamically configured dependencies. 286 | * The allow/denyList options can be used to limit what modules the runtime 287 | * will install/load. It can use '*' as a wildcard that matches anything. 288 | */ 289 | externalModules: { 290 | // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ 291 | // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ 292 | // palette: { /** Configuration for the Palette Manager */ 293 | // allowInstall: true, /** Enable the Palette Manager in the editor */ 294 | // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ 295 | // allowList: [], 296 | // denyList: [] 297 | // }, 298 | // modules: { /** Configuration for node-specified modules */ 299 | // allowInstall: true, 300 | // allowList: [], 301 | // denyList: [] 302 | // } 303 | }, 304 | 305 | 306 | /******************************************************************************* 307 | * Editor Settings 308 | * - disableEditor 309 | * - editorTheme 310 | ******************************************************************************/ 311 | 312 | /** The following property can be used to disable the editor. The admin API 313 | * is not affected by this option. To disable both the editor and the admin 314 | * API, use either the httpRoot or httpAdminRoot properties 315 | */ 316 | //disableEditor: false, 317 | 318 | /** Customising the editor 319 | * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes 320 | * for all available options. 321 | */ 322 | editorTheme: { 323 | /** The following property can be used to set a custom theme for the editor. 324 | * See https://github.com/node-red-contrib-themes/theme-collection for 325 | * a collection of themes to chose from. 326 | */ 327 | //theme: "", 328 | palette: { 329 | /** The following property can be used to order the categories in the editor 330 | * palette. If a node's category is not in the list, the category will get 331 | * added to the end of the palette. 332 | * If not set, the following default order is used: 333 | */ 334 | //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], 335 | }, 336 | projects: { 337 | /** To enable the Projects feature, set this value to true */ 338 | enabled: false, 339 | workflow: { 340 | /** Set the default projects workflow mode. 341 | * - manual - you must manually commit changes 342 | * - auto - changes are automatically committed 343 | * This can be overridden per-user from the 'Git config' 344 | * section of 'User Settings' within the editor 345 | */ 346 | mode: "manual" 347 | } 348 | }, 349 | codeEditor: { 350 | /** Select the text editor component used by the editor. 351 | * Defaults to "ace", but can be set to "ace" or "monaco" 352 | */ 353 | lib: "ace", 354 | options: { 355 | /** The follow options only apply if the editor is set to "monaco" 356 | * 357 | * theme - must match the file name of a theme in 358 | * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme 359 | * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" 360 | */ 361 | theme: "vs", 362 | /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. 363 | * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html 364 | */ 365 | //fontSize: 14, 366 | //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", 367 | //fontLigatures: true, 368 | } 369 | } 370 | }, 371 | 372 | /******************************************************************************* 373 | * Node Settings 374 | * - fileWorkingDirectory 375 | * - functionGlobalContext 376 | * - functionExternalModules 377 | * - nodeMessageBufferMaxLength 378 | * - ui (for use with Node-RED Dashboard) 379 | * - debugUseColors 380 | * - debugMaxLength 381 | * - execMaxBufferSize 382 | * - httpRequestTimeout 383 | * - mqttReconnectTime 384 | * - serialReconnectTime 385 | * - socketReconnectTime 386 | * - socketTimeout 387 | * - tcpMsgQueueSize 388 | * - inboundWebSocketTimeout 389 | * - tlsConfigDisableLocalFiles 390 | * - webSocketNodeVerifyClient 391 | ******************************************************************************/ 392 | 393 | /** The working directory to handle relative file paths from within the File nodes 394 | * defaults to the working directory of the Node-RED process. 395 | */ 396 | //fileWorkingDirectory: "", 397 | 398 | /** Allow the Function node to load additional npm modules directly */ 399 | functionExternalModules: true, 400 | 401 | /** The following property can be used to set predefined values in Global Context. 402 | * This allows extra node modules to be made available with in Function node. 403 | * For example, the following: 404 | * functionGlobalContext: { os:require('os') } 405 | * will allow the `os` module to be accessed in a Function node using: 406 | * global.get("os") 407 | */ 408 | functionGlobalContext: { 409 | os:require('os'), 410 | path:require('path'), 411 | fs:require('fs'), 412 | 413 | }, 414 | 415 | /** The maximum number of messages nodes will buffer internally as part of their 416 | * operation. This applies across a range of nodes that operate on message sequences. 417 | * defaults to no limit. A value of 0 also means no limit is applied. 418 | */ 419 | //nodeMessageBufferMaxLength: 0, 420 | 421 | /** If you installed the optional node-red-dashboard you can set it's path 422 | * relative to httpNodeRoot 423 | * Other optional properties include 424 | * readOnly:{boolean}, 425 | * middleware:{function or array}, (req,res,next) - http middleware 426 | * ioMiddleware:{function or array}, (socket,next) - socket.io middleware 427 | */ 428 | //ui: { path: "ui" }, 429 | ui: { path: "" }, 430 | /** Colourise the console output of the debug node */ 431 | //debugUseColors: true, 432 | 433 | /** The maximum length, in characters, of any message sent to the debug sidebar tab */ 434 | debugMaxLength: 1000, 435 | 436 | /** Maximum buffer size for the exec node. Defaults to 10Mb */ 437 | //execMaxBufferSize: 10000000, 438 | 439 | /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ 440 | //httpRequestTimeout: 120000, 441 | 442 | /** Retry time in milliseconds for MQTT connections */ 443 | mqttReconnectTime: 15000, 444 | 445 | /** Retry time in milliseconds for Serial port connections */ 446 | serialReconnectTime: 15000, 447 | 448 | /** Retry time in milliseconds for TCP socket connections */ 449 | //socketReconnectTime: 10000, 450 | 451 | /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ 452 | //socketTimeout: 120000, 453 | 454 | /** Maximum number of messages to wait in queue while attempting to connect to TCP socket 455 | * defaults to 1000 456 | */ 457 | //tcpMsgQueueSize: 2000, 458 | 459 | /** Timeout in milliseconds for inbound WebSocket connections that do not 460 | * match any configured node. Defaults to 5000 461 | */ 462 | //inboundWebSocketTimeout: 5000, 463 | 464 | /** To disable the option for using local files for storing keys and 465 | * certificates in the TLS configuration node, set this to true. 466 | */ 467 | //tlsConfigDisableLocalFiles: true, 468 | 469 | /** The following property can be used to verify websocket connection attempts. 470 | * This allows, for example, the HTTP request headers to be checked to ensure 471 | * they include valid authentication information. 472 | */ 473 | //webSocketNodeVerifyClient: function(info) { 474 | // /** 'info' has three properties: 475 | // * - origin : the value in the Origin header 476 | // * - req : the HTTP request 477 | // * - secure : true if req.connection.authorized or req.connection.encrypted is set 478 | // * 479 | // * The function should return true if the connection should be accepted, false otherwise. 480 | // * 481 | // * Alternatively, if this function is defined to accept a second argument, callback, 482 | // * it can be used to verify the client asynchronously. 483 | // * The callback takes three arguments: 484 | // * - result : boolean, whether to accept the connection or not 485 | // * - code : if result is false, the HTTP error status to return 486 | // * - reason: if result is false, the HTTP reason string to return 487 | // */ 488 | //}, 489 | } 490 | -------------------------------------------------------------------------------- /update/mini/Arducam.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | 4 | try: 5 | import v4l2 6 | except Exception as e: 7 | print(e) 8 | print("Try to install v4l2-fix") 9 | try: 10 | from pip import main as pipmain 11 | except ImportError: 12 | from pip._internal import main as pipmain 13 | pipmain(['install', 'v4l2-fix']) 14 | print("\nTry to run the focus program again.") 15 | exit(0) 16 | 17 | import fcntl 18 | import errno 19 | 20 | # # Type 21 | # v4l2.V4L2_CTRL_TYPE_INTEGER 22 | # v4l2.V4L2_CTRL_TYPE_BOOLEAN 23 | # v4l2.V4L2_CTRL_TYPE_MENU 24 | # v4l2.V4L2_CTRL_TYPE_BUTTON 25 | # v4l2.V4L2_CTRL_TYPE_INTEGER64 26 | # v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 27 | # # Flags 28 | # v4l2.V4L2_CTRL_FLAG_DISABLED 29 | # v4l2.V4L2_CTRL_FLAG_GRABBED 30 | # v4l2.V4L2_CTRL_FLAG_READ_ONLY 31 | # v4l2.V4L2_CTRL_FLAG_UPDATE 32 | # v4l2.V4L2_CTRL_FLAG_INACTIVE 33 | # v4l2.V4L2_CTRL_FLAG_SLIDER 34 | 35 | def assert_valid_queryctrl(queryctrl): 36 | return queryctrl.type & ( 37 | v4l2.V4L2_CTRL_TYPE_INTEGER 38 | | v4l2.V4L2_CTRL_TYPE_BOOLEAN 39 | | v4l2.V4L2_CTRL_TYPE_MENU 40 | | v4l2.V4L2_CTRL_TYPE_BUTTON 41 | | v4l2.V4L2_CTRL_TYPE_INTEGER64 42 | | v4l2.V4L2_CTRL_TYPE_CTRL_CLASS 43 | | 7 44 | | 8 45 | | 9 46 | ) and queryctrl.flags & ( 47 | v4l2.V4L2_CTRL_FLAG_DISABLED 48 | | v4l2.V4L2_CTRL_FLAG_GRABBED 49 | | v4l2.V4L2_CTRL_FLAG_READ_ONLY 50 | | v4l2.V4L2_CTRL_FLAG_UPDATE 51 | | v4l2.V4L2_CTRL_FLAG_INACTIVE 52 | | v4l2.V4L2_CTRL_FLAG_SLIDER 53 | ) 54 | 55 | def get_device_controls_menu(fd, queryctrl): 56 | querymenu = v4l2.v4l2_querymenu(queryctrl.id, queryctrl.minimum) 57 | while querymenu.index <= queryctrl.maximum: 58 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYMENU, querymenu) 59 | yield querymenu 60 | querymenu.index += 1 61 | 62 | def get_device_controls_by_class(fd, control_class): 63 | # enumeration by control class 64 | queryctrl = v4l2.v4l2_queryctrl(control_class | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 65 | while True: 66 | try: 67 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 68 | except IOError as e: 69 | assert e.errno == errno.EINVAL 70 | break 71 | if v4l2.V4L2_CTRL_ID2CLASS(queryctrl.id) != control_class: 72 | break 73 | yield queryctrl 74 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id | v4l2.V4L2_CTRL_FLAG_NEXT_CTRL) 75 | 76 | def getdict(struct): 77 | val = dict((field, getattr(struct, field)) for field, _ in struct._fields_) 78 | val.pop("reserved") 79 | return val 80 | 81 | def get_device_controls(fd): 82 | # original enumeration method 83 | queryctrl = v4l2.v4l2_queryctrl(v4l2.V4L2_CID_BASE) 84 | while queryctrl.id < v4l2.V4L2_CID_LASTP1: 85 | try: 86 | fcntl.ioctl(fd, v4l2.VIDIOC_QUERYCTRL, queryctrl) 87 | print(queryctrl.name) 88 | except IOError as e: 89 | # this predefined control is not supported by this device 90 | assert e.errno == errno.EINVAL 91 | queryctrl.id += 1 92 | continue 93 | queryctrl = v4l2.v4l2_queryctrl(queryctrl.id + 1) 94 | 95 | def get_ctrls(vd): 96 | ctrls = [] 97 | # enumeration by control class 98 | for class_ in (v4l2.V4L2_CTRL_CLASS_USER, v4l2.V4L2_CTRL_CLASS_MPEG, v4l2.V4L2_CTRL_CLASS_CAMERA): 99 | for queryctrl in get_device_controls_by_class(vd, class_): 100 | ctrl = getdict(queryctrl) 101 | if queryctrl.type == v4l2.V4L2_CTRL_TYPE_MENU: 102 | ctrl["menu"] = [] 103 | for querymenu in get_device_controls_menu(vd, queryctrl): 104 | # print(querymenu.name) 105 | ctrl["menu"].append(querymenu.name) 106 | 107 | if queryctrl.type == 9: 108 | ctrl["menu"] = [] 109 | for querymenu in get_device_controls_menu(vd, queryctrl): 110 | ctrl["menu"].append(querymenu.index) 111 | ctrls.append(ctrl) 112 | return ctrls 113 | 114 | def set_ctrl(vd, id, value): 115 | ctrl = v4l2.v4l2_control() 116 | ctrl.id = id 117 | ctrl.value = value 118 | try: 119 | fcntl.ioctl(vd, v4l2.VIDIOC_S_CTRL, ctrl) 120 | except IOError as e: 121 | print(e) 122 | 123 | def get_ctrl(vd, id): 124 | ctrl = v4l2.v4l2_control() 125 | ctrl.id = id 126 | try: 127 | fcntl.ioctl(vd, v4l2.VIDIOC_G_CTRL, ctrl) 128 | except IOError as e: 129 | print(e) 130 | return None 131 | return ctrl.value 132 | 133 | 134 | class Focuser: 135 | FOCUS_ID = 0x009a090a 136 | dev = None 137 | 138 | def __init__(self, dev=0): 139 | self.focus_value = 0 140 | self.dev = dev 141 | 142 | if type(dev) == int or (type(dev) == str and dev.isnumeric()): 143 | self.dev = "/dev/video{}".format(dev) 144 | 145 | self.fd = open(self.dev, 'r') 146 | self.ctrls = get_ctrls(self.fd) 147 | self.hasFocus = False 148 | for ctrl in self.ctrls: 149 | if ctrl['id'] == Focuser.FOCUS_ID: 150 | self.hasFocus = True 151 | self.opts[Focuser.OPT_FOCUS]["MIN_VALUE"] = ctrl['minimum'] 152 | self.opts[Focuser.OPT_FOCUS]["MAX_VALUE"] = ctrl['maximum'] 153 | self.opts[Focuser.OPT_FOCUS]["DEF_VALUE"] = ctrl['default'] 154 | self.focus_value = get_ctrl(self.fd, Focuser.FOCUS_ID) 155 | 156 | if not self.hasFocus: 157 | raise RuntimeError("Device {} has no focus_absolute control.".format(self.dev)) 158 | 159 | def read(self): 160 | return self.focus_value 161 | 162 | def write(self, value): 163 | self.focus_value = value 164 | # os.system("v4l2-ctl -d {} -c focus_absolute={}".format(self.dev, value)) 165 | set_ctrl(self.fd, Focuser.FOCUS_ID, value) 166 | 167 | OPT_BASE = 0x1000 168 | OPT_FOCUS = OPT_BASE | 0x01 169 | OPT_ZOOM = OPT_BASE | 0x02 170 | OPT_MOTOR_X = OPT_BASE | 0x03 171 | OPT_MOTOR_Y = OPT_BASE | 0x04 172 | OPT_IRCUT = OPT_BASE | 0x05 173 | opts = { 174 | OPT_FOCUS : { 175 | "MIN_VALUE": 0, 176 | "MAX_VALUE": 1000, 177 | "DEF_VALUE": 0, 178 | }, 179 | } 180 | def reset(self,opt,flag = 1): 181 | info = self.opts[opt] 182 | if info == None or info["DEF_VALUE"] == None: 183 | return 184 | self.set(opt,info["DEF_VALUE"]) 185 | 186 | def get(self,opt,flag = 0): 187 | info = self.opts[opt] 188 | return self.read() 189 | 190 | def set(self,opt,value,flag = 1): 191 | info = self.opts[opt] 192 | if value > info["MAX_VALUE"]: 193 | value = info["MAX_VALUE"] 194 | elif value < info["MIN_VALUE"]: 195 | value = info["MIN_VALUE"] 196 | self.write(value) 197 | print("write: {}".format(value)) 198 | 199 | def __del__(self): 200 | self.fd.close() 201 | 202 | pass 203 | -------------------------------------------------------------------------------- /update/mini/OpenScan.py: -------------------------------------------------------------------------------- 1 | basepath = '/home/pi/OpenScan/' 2 | from os.path import isfile 3 | 4 | def load_bool(name): 5 | filename = basepath+'settings/'+name 6 | if not isfile(filename): 7 | return 8 | with open(filename, 'r') as file: 9 | value = file.read().replace('\n','') 10 | if value == '1' or value == 'True' or value =='true': 11 | value = True 12 | else: 13 | value = False 14 | return value 15 | 16 | def load_str(name): 17 | filename = basepath+'settings/'+name 18 | if not isfile(filename): 19 | return 20 | with open(filename, 'r') as file: 21 | value = file.read().replace('\n','') 22 | return value 23 | 24 | def load_int(name): 25 | filename = basepath+'settings/'+name 26 | if not isfile(filename): 27 | return 28 | with open(filename, 'r') as file: 29 | value = int(file.read().replace('\n','')) 30 | return value 31 | 32 | def load_float(name): 33 | filename = basepath+'settings/'+name 34 | if not isfile(filename): 35 | return 36 | with open(filename, 'r') as file: 37 | value = float(file.read().replace('\n','')) 38 | return value 39 | 40 | def save(name, value): 41 | filename = basepath+'settings/'+name 42 | with open(filename, 'w+') as file: 43 | file.write(str(value)) 44 | return 45 | 46 | def OpenScanCloud(cmd, msg): 47 | from requests import get 48 | osc_user = 'openscan' 49 | osc_pw = 'free' 50 | osc_server = 'http://openscanfeedback.dnsuser.de:1334/' 51 | 52 | try: 53 | r = get(osc_server + cmd, auth=(osc_user, osc_pw), params=msg) 54 | except: 55 | r = type('obj', (object,), {'status_code' : 404, 'text':None}) 56 | return r 57 | 58 | def camera(cmd, msg = {}): 59 | from requests import get 60 | flask = 'http://127.0.0.1:1312/' 61 | r = get(flask + cmd, params=msg) 62 | return r.status_code 63 | 64 | def motorrun(motor,angle): 65 | import RPi.GPIO as GPIO 66 | from time import sleep 67 | from math import cos 68 | GPIO.setwarnings(False) 69 | GPIO.setmode(GPIO.BCM) 70 | 71 | spr = load_int(motor + '_stepsperrotation') 72 | dirpin = load_int('pin_' + motor + '_dir') 73 | steppin = load_int('pin_' + motor +'_step') 74 | dir = load_int(motor + '_dir') 75 | ramp = load_int(motor + '_accramp') 76 | acc = load_float(motor + '_acc') 77 | delay_init = load_float(motor + '_delay') 78 | delay = delay_init 79 | 80 | step_count=int(angle*spr/360) * dir 81 | GPIO.setup(dirpin, GPIO.OUT) 82 | GPIO.setup(steppin, GPIO.OUT) 83 | if (step_count>0): 84 | GPIO.output(dirpin, GPIO.HIGH) 85 | if(step_count<0): 86 | GPIO.output(dirpin, GPIO.LOW) 87 | step_count=-step_count 88 | for x in range(step_count): 89 | GPIO.output(steppin, GPIO.HIGH) 90 | if x<=ramp and x<=step_count/2: 91 | delay = delay_init * (1 + -1/acc*cos(1*(ramp-x)/ramp)+1/acc) 92 | #delay=delay_init+(ramp-x)*(delay_init)/acc 93 | elif step_count-x<=ramp and x>step_count/2: 94 | delay = delay_init * (1-1/acc*cos(1*(ramp+x-step_count)/ramp)+1/acc) 95 | #delay=delay_init+(ramp-step_count+x)*(delay_init)/acc 96 | else: 97 | delay = delay_init 98 | sleep(delay) 99 | GPIO.output(steppin, GPIO.LOW) 100 | sleep(delay) 101 | 102 | def ringlight(number,state): 103 | import RPi.GPIO as GPIO 104 | pin = load_int('pin_ringlight' + str(number)) 105 | GPIO.setwarnings(False) 106 | GPIO.setmode(GPIO.BCM) 107 | GPIO.setup(pin, GPIO.OUT) 108 | GPIO.output(pin, state) 109 | 110 | def take_photo(file): 111 | from os import system 112 | filepath = basepath + file 113 | 114 | model=load_str('model') 115 | 116 | 117 | 118 | shutter = str(load_int('cam_shutter')) 119 | saturation = load_str('cam_saturation') 120 | contrast = load_str('cam_contrast') 121 | awbg_red = load_str('cam_awbg_red') 122 | awbg_blue = load_str('cam_awbg_blue') 123 | gain = load_str('cam_gain') 124 | quality = load_int('cam_jpeg_quality') 125 | filepath2 = '/home/pi/OpenScan/tmp/tmp.jpg' 126 | #width = load_str('cam_resx') 127 | #height = load_str('cam_resy') 128 | timeout = load_str('cam_timeout') 129 | cropx = load_int('cam_cropx')/200 130 | cropy = load_int('cam_cropy')/200 131 | rotation = load_int('cam_rotation') 132 | AF = load_bool('cam_AFmode') 133 | camera = load_str('camera') 134 | 135 | 136 | if camera == 'imx519' and AF == True: 137 | autofocus = ' --autofocus ' 138 | else: 139 | autofocus = '' 140 | 141 | 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' 142 | # 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 143 | system(cmd) 144 | return cmd 145 | 146 | def get_points(samples=1): 147 | from math import pi, sqrt, acos, atan2, cos, sin 148 | 149 | points = [] 150 | phi = pi * (3. - sqrt(5.)) 151 | for i in range(int(samples)): 152 | y = 1 - (i / float(samples - 1)) * 2 153 | radius = sqrt(1 - y * y) 154 | theta = phi * i 155 | x = cos(theta) * radius 156 | z = sin(theta) * radius 157 | r=sqrt(x*x+y*y+z*z) 158 | theta_neu=acos(z/r)*180/pi 159 | phi_neu=atan2(y,x)*180/pi 160 | points.append((theta_neu-90,phi_neu)) 161 | points.sort() 162 | return points 163 | 164 | def create_coordinates(angle_min, angle_max,point_count): 165 | point_count_final=point_count 166 | if angle_max < angle_min: 167 | a = angle_min 168 | angle_min = angle_max 169 | angle_max = a 170 | point_count=point_count*90/(angle_max-angle_min) 171 | actual_points=0 172 | while actual_pointsangle_min and x20: 181 | point_count=point_count+3 182 | else: 183 | point_count=point_count+1 184 | return filtered 185 | 186 | -------------------------------------------------------------------------------- /update/mini/config.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://rpf.io/configtxt 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | 6 | # uncomment if you get no picture on HDMI for a default "safe" mode 7 | #hdmi_safe=1 8 | hdmi_blanking=2 9 | 10 | # uncomment the following to adjust overscan. Use positive numbers if console 11 | # goes off screen, and negative if there is too much border 12 | #overscan_left=16 13 | #overscan_right=16 14 | #overscan_top=16 15 | #overscan_bottom=16 16 | 17 | # uncomment to force a console size. By default it will be display's size minus 18 | # overscan. 19 | #framebuffer_width=1280 20 | #framebuffer_height=720 21 | 22 | # uncomment if hdmi display is not detected and composite is being output 23 | #hdmi_force_hotplug=1 24 | 25 | # uncomment to force a specific HDMI mode (this will force VGA) 26 | #hdmi_group=1 27 | #hdmi_mode=1 28 | 29 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 30 | # DMT (computer monitor) modes 31 | #hdmi_drive=2 32 | 33 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 34 | # no display 35 | #config_hdmi_boost=4 36 | 37 | # uncomment for composite PAL 38 | #sdtv_mode=2 39 | 40 | #uncomment to overclock the arm. 700 MHz is the default. 41 | #arm_freq=800 42 | 43 | # Uncomment some or all of these to enable the optional hardware interfaces 44 | #dtparam=i2c_arm=on 45 | #dtparam=i2s=on 46 | #dtparam=spi=on 47 | 48 | # Uncomment this to enable infrared communication. 49 | #dtoverlay=gpio-ir,gpio_pin=17 50 | #dtoverlay=gpio-ir-tx,gpio_pin=18 51 | 52 | # Additional overlays and parameters are documented /boot/overlays/README 53 | 54 | # Enable audio (loads snd_bcm2835) 55 | dtparam=audio=on 56 | 57 | # Automatically load overlays for detected cameras 58 | camera_auto_detect=0 59 | 60 | # Automatically load overlays for detected DSI displays 61 | display_auto_detect=1 62 | 63 | # Enable DRM VC4 V3D driver 64 | #dtoverlay=vc4-kms-v3d 65 | max_framebuffers=2 66 | 67 | # Disable compensation for displays with overscan 68 | disable_overscan=1 69 | 70 | [cm4] 71 | # Enable host mode on the 2711 built-in XHCI USB controller. 72 | # This line should be removed if the legacy DWC2 controller is required 73 | # (e.g. for USB device mode) or if USB support is not required. 74 | otg_mode=1 75 | 76 | [pi4] 77 | # Run as fast as firmware / board allows 78 | arm_boost=1 79 | 80 | [all] 81 | 82 | camera_auto_detect=0 83 | gpu_mem=256 84 | dtoverlay=vc4-fkms-v3d 85 | dtoverlay=imx519,media-controller=0 86 | dtoverlay=imx519 87 | -------------------------------------------------------------------------------- /update/mini/fla.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, make_response, jsonify, request, abort 2 | from PIL import Image 3 | import gphoto2 as gp 4 | from time import sleep 5 | import shutil 6 | from OpenScan import load_int, load_float, load_bool 7 | import RPi.GPIO as GPIO 8 | 9 | GPIO.setwarnings(False) 10 | GPIO.setmode(GPIO.BCM) 11 | 12 | app = Flask(__name__) 13 | 14 | basedir = '/home/pi/OpenScan/' 15 | 16 | ################################################################################################################### 17 | @app.route('/gphoto_init', methods=['get']) 18 | def gphoto_init(): 19 | global camera 20 | camera = gp.Camera() 21 | camera.init() 22 | return ({}, 200) 23 | ################################################################################################################### 24 | @app.route('/gphoto_preview', methods=['get']) 25 | def gphoto_preview(): 26 | filepath = str(request.args.get('filepath')) 27 | camera_file = gp.gp_camera_capture_preview(camera)[1] 28 | target = basedir + filepath 29 | camera_file.save(target) 30 | return ({}, 200) 31 | ################################################################################################################### 32 | @app.route('/gphoto_capture', methods=['get']) 33 | def gphoto_capture(): 34 | filepath = str(request.args.get('filepath')) 35 | file_path = camera.capture(gp.GP_CAPTURE_IMAGE) 36 | camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) 37 | camera_file.save(basedir + filepath) 38 | return ({}, 200) 39 | ################################################################################################################### 40 | @app.route('/gphoto_test', methods=['get']) 41 | def gphoto_test(): 42 | text = camera.get_summary() 43 | return ({}, 200) 44 | ################################################################################################################### 45 | @app.route('/gphoto_exit', methods=['get']) 46 | def gphoto_exit(): 47 | global camera 48 | camera.exit() 49 | return ({}, 200) 50 | ################################################################################################################### 51 | @app.route('/crop', methods=['get']) 52 | def crop(): 53 | downscale_threshold = 1000 54 | filepath_in = basedir + str(request.args.get('filepath_in')) 55 | filepath_out = basedir + str(request.args.get('filepath_out')) 56 | cropx = int(request.args.get('cropx'))/200 57 | cropy = int(request.args.get('cropy'))/200 58 | rotation = int(request.args.get('rotation')) 59 | preview = str(request.args.get('preview')) 60 | 61 | with Image.open(filepath_in) as img: 62 | w,h = img.size 63 | if cropx != 0 or cropy != 0: 64 | img = img.crop((w*cropx, h*cropy, w * (1-cropx), h * (1-cropy))) 65 | if rotation == 90: 66 | img = img.transpose(Image.ROTATE_90) 67 | elif rotation == 180: 68 | img= img.transpose(Image.ROTATE_180) 69 | elif rotation == 270: 70 | img= img.transpose(Image.ROTATE_270) 71 | if preview == "True": 72 | w,h = img.size 73 | if w > downscale_threshold or h > downscale_threshold: 74 | downscale = max(w/downscale_threshold,h/downscale_threshold) 75 | img = img.resize((int(w/downscale),int(h/downscale)),Image.ANTIALIAS) 76 | img.save(filepath_out, quality=95, subsampling=0) 77 | return ({}, 200) 78 | 79 | ################################################################################################################### 80 | @app.route('/external_capture', methods=['get']) 81 | def external_capture(): 82 | pin = load_int('pin_external') 83 | delay_before = load_float('cam_delay_before') 84 | timeout = load_float('cam_timeout')/1000 85 | delay_after = load_float('cam_delay_after') 86 | GPIO.setup(pin, GPIO.OUT) 87 | GPIO.output(pin, GPIO.LOW) 88 | sleep(delay_before) 89 | GPIO.output(pin, GPIO.HIGH) 90 | sleep(timeout) 91 | GPIO.output(pin, GPIO.LOW) 92 | sleep(delay_after) 93 | return ({}, 200) 94 | 95 | 96 | 97 | 98 | if __name__ == '__main__': 99 | app.run(host='127.0.0.1', port=1312, debug=False, threaded=True) 100 | # app.run(host='0.0.0.0', port=1312, debug=False, threaded=True) 101 | -------------------------------------------------------------------------------- /update/mini/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node-RED Settings created at Mon, 24 Jan 2022 08:17:31 GMT 3 | * 4 | * It can contain any valid JavaScript code that will get run when Node-RED 5 | * is started. 6 | * 7 | * Lines that start with // are commented out. 8 | * Each entry should be separated from the entries above and below by a comma ',' 9 | * 10 | * For more information about individual settings, refer to the documentation: 11 | * https://nodered.org/docs/user-guide/runtime/configuration 12 | * 13 | * The settings are split into the following sections: 14 | * - Flow File and User Directory Settings 15 | * - Security 16 | * - Server Settings 17 | * - Runtime Settings 18 | * - Editor Settings 19 | * - Node Settings 20 | * 21 | **/ 22 | 23 | module.exports = { 24 | 25 | /******************************************************************************* 26 | * Flow File and User Directory Settings 27 | * - flowFile 28 | * - credentialSecret 29 | * - flowFilePretty 30 | * - userDir 31 | * - nodesDir 32 | ******************************************************************************/ 33 | 34 | /** The file containing the flows. If not set, defaults to flows_.json **/ 35 | flowFile: "flows.json", 36 | 37 | /** By default, credentials are encrypted in storage using a generated key. To 38 | * specify your own secret, set the following property. 39 | * If you want to disable encryption of credentials, set this property to false. 40 | * Note: once you set this property, do not change it - doing so will prevent 41 | * node-red from being able to decrypt your existing credentials and they will be 42 | * lost. 43 | */ 44 | credentialSecret: false, 45 | 46 | /** By default, the flow JSON will be formatted over multiple lines making 47 | * it easier to compare changes when using version control. 48 | * To disable pretty-printing of the JSON set the following property to false. 49 | */ 50 | flowFilePretty: true, 51 | 52 | /** By default, all user data is stored in a directory called `.node-red` under 53 | * the user's home directory. To use a different location, the following 54 | * property can be used 55 | */ 56 | //userDir: '/home/nol/.node-red/', 57 | userDir: '/home/pi/OpenScan/settings/.node-red/', 58 | /** Node-RED scans the `nodes` directory in the userDir to find local node files. 59 | * The following property can be used to specify an additional directory to scan. 60 | */ 61 | //nodesDir: '/home/nol/.node-red/nodes', 62 | 63 | /******************************************************************************* 64 | * Security 65 | * - adminAuth 66 | * - https 67 | * - httpsRefreshInterval 68 | * - requireHttps 69 | * - httpNodeAuth 70 | * - httpStaticAuth 71 | ******************************************************************************/ 72 | 73 | /** To password protect the Node-RED editor and admin API, the following 74 | * property can be used. See http://nodered.org/docs/security.html for details. 75 | */ 76 | //adminAuth: { 77 | // type: "credentials", 78 | // users: [{ 79 | // username: "admin", 80 | // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", 81 | // permissions: "*" 82 | // }] 83 | //}, 84 | 85 | /** The following property can be used to enable HTTPS 86 | * This property can be either an object, containing both a (private) key 87 | * and a (public) certificate, or a function that returns such an object. 88 | * See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener 89 | * for details of its contents. 90 | */ 91 | 92 | /** Option 1: static object */ 93 | //https: { 94 | // key: require("fs").readFileSync('privkey.pem'), 95 | // cert: require("fs").readFileSync('cert.pem') 96 | //}, 97 | 98 | /** Option 2: function that returns the HTTP configuration object */ 99 | // https: function() { 100 | // // This function should return the options object, or a Promise 101 | // // that resolves to the options object 102 | // return { 103 | // key: require("fs").readFileSync('privkey.pem'), 104 | // cert: require("fs").readFileSync('cert.pem') 105 | // } 106 | // }, 107 | 108 | /** If the `https` setting is a function, the following setting can be used 109 | * to set how often, in hours, the function will be called. That can be used 110 | * to refresh any certificates. 111 | */ 112 | //httpsRefreshInterval : 12, 113 | 114 | /** The following property can be used to cause insecure HTTP connections to 115 | * be redirected to HTTPS. 116 | */ 117 | //requireHttps: true, 118 | 119 | /** To password protect the node-defined HTTP endpoints (httpNodeRoot), 120 | * including node-red-dashboard, or the static content (httpStatic), the 121 | * following properties can be used. 122 | * The `pass` field is a bcrypt hash of the password. 123 | * See http://nodered.org/docs/security.html#generating-the-password-hash 124 | */ 125 | //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 126 | //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 127 | 128 | /******************************************************************************* 129 | * Server Settings 130 | * - uiPort 131 | * - uiHost 132 | * - apiMaxLength 133 | * - httpServerOptions 134 | * - httpAdminRoot 135 | * - httpAdminMiddleware 136 | * - httpNodeRoot 137 | * - httpNodeCors 138 | * - httpNodeMiddleware 139 | * - httpStatic 140 | ******************************************************************************/ 141 | 142 | /** the tcp port that the Node-RED web server is listening on */ 143 | // uiPort: process.env.PORT || 1880, 144 | uiPort: process.env.PORT || 80, 145 | /** By default, the Node-RED UI accepts connections on all IPv4 interfaces. 146 | * To listen on all IPv6 addresses, set uiHost to "::", 147 | * The following property can be used to listen on a specific interface. For 148 | * example, the following would only allow connections from the local machine. 149 | */ 150 | //uiHost: "127.0.0.1", 151 | 152 | /** The maximum size of HTTP request that will be accepted by the runtime api. 153 | * Default: 5mb 154 | */ 155 | //apiMaxLength: '5mb', 156 | 157 | /** The following property can be used to pass custom options to the Express.js 158 | * server used by Node-RED. For a full list of available options, refer 159 | * to http://expressjs.com/en/api.html#app.settings.table 160 | */ 161 | //httpServerOptions: { }, 162 | 163 | /** By default, the Node-RED UI is available at http://localhost:1880/ 164 | * The following property can be used to specify a different root path. 165 | * If set to false, this is disabled. 166 | */ 167 | //httpAdminRoot: '/admin', 168 | httpAdminRoot: '/editor', 169 | /** The following property can be used to add a custom middleware function 170 | * in front of all admin http routes. For example, to set custom http 171 | * headers. It can be a single function or an array of middleware functions. 172 | */ 173 | // httpAdminMiddleware: function(req,res,next) { 174 | // // Set the X-Frame-Options header to limit where the editor 175 | // // can be embedded 176 | // //res.set('X-Frame-Options', 'sameorigin'); 177 | // next(); 178 | // }, 179 | 180 | 181 | /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. 182 | * By default, these are served relative to '/'. The following property 183 | * can be used to specifiy a different root path. If set to false, this is 184 | * disabled. 185 | */ 186 | //httpNodeRoot: '/red-nodes', 187 | 188 | /** The following property can be used to configure cross-origin resource sharing 189 | * in the HTTP nodes. 190 | * See https://github.com/troygoode/node-cors#configuration-options for 191 | * details on its contents. The following is a basic permissive set of options: 192 | */ 193 | //httpNodeCors: { 194 | // origin: "*", 195 | // methods: "GET,PUT,POST,DELETE" 196 | //}, 197 | 198 | /** If you need to set an http proxy please set an environment variable 199 | * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. 200 | * For example - http_proxy=http://myproxy.com:8080 201 | * (Setting it here will have no effect) 202 | * You may also specify no_proxy (or NO_PROXY) to supply a comma separated 203 | * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk 204 | */ 205 | 206 | /** The following property can be used to add a custom middleware function 207 | * in front of all http in nodes. This allows custom authentication to be 208 | * applied to all http in nodes, or any other sort of common request processing. 209 | * It can be a single function or an array of middleware functions. 210 | */ 211 | //httpNodeMiddleware: function(req,res,next) { 212 | // // Handle/reject the request, or pass it on to the http in node by calling next(); 213 | // // Optionally skip our rawBodyParser by setting this to true; 214 | // //req.skipRawBodyParser = true; 215 | // next(); 216 | //}, 217 | 218 | /** When httpAdminRoot is used to move the UI to a different root path, the 219 | * following property can be used to identify a directory of static content 220 | * that should be served at http://localhost:1880/. 221 | */ 222 | //httpStatic: '/home/nol/node-red-static/', 223 | httpStatic: '/home/pi/OpenScan/', 224 | /******************************************************************************* 225 | * Runtime Settings 226 | * - lang 227 | * - logging 228 | * - contextStorage 229 | * - exportGlobalContextKeys 230 | * - externalModules 231 | ******************************************************************************/ 232 | 233 | /** Uncomment the following to run node-red in your preferred language. 234 | * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko 235 | * Some languages are more complete than others. 236 | */ 237 | // lang: "de", 238 | 239 | /** Configure the logging output */ 240 | logging: { 241 | /** Only console logging is currently supported */ 242 | console: { 243 | /** Level of logging to be recorded. Options are: 244 | * fatal - only those errors which make the application unusable should be recorded 245 | * error - record errors which are deemed fatal for a particular request + fatal errors 246 | * warn - record problems which are non fatal + errors + fatal errors 247 | * info - record information about the general running of the application + warn + error + fatal errors 248 | * debug - record information which is more verbose than info + info + warn + error + fatal errors 249 | * trace - record very detailed logging + debug + info + warn + error + fatal errors 250 | * off - turn off all logging (doesn't affect metrics or audit) 251 | */ 252 | level: "info", 253 | /** Whether or not to include metric events in the log output */ 254 | metrics: false, 255 | /** Whether or not to include audit events in the log output */ 256 | audit: false 257 | } 258 | }, 259 | 260 | /** Context Storage 261 | * The following property can be used to enable context storage. The configuration 262 | * provided here will enable file-based context that flushes to disk every 30 seconds. 263 | * Refer to the documentation for further options: https://nodered.org/docs/api/context/ 264 | */ 265 | //contextStorage: { 266 | // default: { 267 | // module:"localfilesystem" 268 | // }, 269 | //}, 270 | 271 | /** `global.keys()` returns a list of all properties set in global context. 272 | * This allows them to be displayed in the Context Sidebar within the editor. 273 | * In some circumstances it is not desirable to expose them to the editor. The 274 | * following property can be used to hide any property set in `functionGlobalContext` 275 | * from being list by `global.keys()`. 276 | * By default, the property is set to false to avoid accidental exposure of 277 | * their values. Setting this to true will cause the keys to be listed. 278 | */ 279 | exportGlobalContextKeys: false, 280 | 281 | /** Configure how the runtime will handle external npm modules. 282 | * This covers: 283 | * - whether the editor will allow new node modules to be installed 284 | * - whether nodes, such as the Function node are allowed to have their 285 | * own dynamically configured dependencies. 286 | * The allow/denyList options can be used to limit what modules the runtime 287 | * will install/load. It can use '*' as a wildcard that matches anything. 288 | */ 289 | externalModules: { 290 | // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ 291 | // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ 292 | // palette: { /** Configuration for the Palette Manager */ 293 | // allowInstall: true, /** Enable the Palette Manager in the editor */ 294 | // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ 295 | // allowList: [], 296 | // denyList: [] 297 | // }, 298 | // modules: { /** Configuration for node-specified modules */ 299 | // allowInstall: true, 300 | // allowList: [], 301 | // denyList: [] 302 | // } 303 | }, 304 | 305 | 306 | /******************************************************************************* 307 | * Editor Settings 308 | * - disableEditor 309 | * - editorTheme 310 | ******************************************************************************/ 311 | 312 | /** The following property can be used to disable the editor. The admin API 313 | * is not affected by this option. To disable both the editor and the admin 314 | * API, use either the httpRoot or httpAdminRoot properties 315 | */ 316 | //disableEditor: false, 317 | 318 | /** Customising the editor 319 | * See https://nodered.org/docs/user-guide/runtime/configuration#editor-themes 320 | * for all available options. 321 | */ 322 | editorTheme: { 323 | /** The following property can be used to set a custom theme for the editor. 324 | * See https://github.com/node-red-contrib-themes/theme-collection for 325 | * a collection of themes to chose from. 326 | */ 327 | //theme: "", 328 | palette: { 329 | /** The following property can be used to order the categories in the editor 330 | * palette. If a node's category is not in the list, the category will get 331 | * added to the end of the palette. 332 | * If not set, the following default order is used: 333 | */ 334 | //categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], 335 | }, 336 | projects: { 337 | /** To enable the Projects feature, set this value to true */ 338 | enabled: false, 339 | workflow: { 340 | /** Set the default projects workflow mode. 341 | * - manual - you must manually commit changes 342 | * - auto - changes are automatically committed 343 | * This can be overridden per-user from the 'Git config' 344 | * section of 'User Settings' within the editor 345 | */ 346 | mode: "manual" 347 | } 348 | }, 349 | codeEditor: { 350 | /** Select the text editor component used by the editor. 351 | * Defaults to "ace", but can be set to "ace" or "monaco" 352 | */ 353 | lib: "ace", 354 | options: { 355 | /** The follow options only apply if the editor is set to "monaco" 356 | * 357 | * theme - must match the file name of a theme in 358 | * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme 359 | * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" 360 | */ 361 | theme: "vs", 362 | /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. 363 | * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html 364 | */ 365 | //fontSize: 14, 366 | //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace", 367 | //fontLigatures: true, 368 | } 369 | } 370 | }, 371 | 372 | /******************************************************************************* 373 | * Node Settings 374 | * - fileWorkingDirectory 375 | * - functionGlobalContext 376 | * - functionExternalModules 377 | * - nodeMessageBufferMaxLength 378 | * - ui (for use with Node-RED Dashboard) 379 | * - debugUseColors 380 | * - debugMaxLength 381 | * - execMaxBufferSize 382 | * - httpRequestTimeout 383 | * - mqttReconnectTime 384 | * - serialReconnectTime 385 | * - socketReconnectTime 386 | * - socketTimeout 387 | * - tcpMsgQueueSize 388 | * - inboundWebSocketTimeout 389 | * - tlsConfigDisableLocalFiles 390 | * - webSocketNodeVerifyClient 391 | ******************************************************************************/ 392 | 393 | /** The working directory to handle relative file paths from within the File nodes 394 | * defaults to the working directory of the Node-RED process. 395 | */ 396 | //fileWorkingDirectory: "", 397 | 398 | /** Allow the Function node to load additional npm modules directly */ 399 | functionExternalModules: true, 400 | 401 | /** The following property can be used to set predefined values in Global Context. 402 | * This allows extra node modules to be made available with in Function node. 403 | * For example, the following: 404 | * functionGlobalContext: { os:require('os') } 405 | * will allow the `os` module to be accessed in a Function node using: 406 | * global.get("os") 407 | */ 408 | functionGlobalContext: { 409 | os:require('os'), 410 | path:require('path'), 411 | fs:require('fs'), 412 | 413 | }, 414 | 415 | /** The maximum number of messages nodes will buffer internally as part of their 416 | * operation. This applies across a range of nodes that operate on message sequences. 417 | * defaults to no limit. A value of 0 also means no limit is applied. 418 | */ 419 | //nodeMessageBufferMaxLength: 0, 420 | 421 | /** If you installed the optional node-red-dashboard you can set it's path 422 | * relative to httpNodeRoot 423 | * Other optional properties include 424 | * readOnly:{boolean}, 425 | * middleware:{function or array}, (req,res,next) - http middleware 426 | * ioMiddleware:{function or array}, (socket,next) - socket.io middleware 427 | */ 428 | //ui: { path: "ui" }, 429 | ui: { path: "" }, 430 | /** Colourise the console output of the debug node */ 431 | //debugUseColors: true, 432 | 433 | /** The maximum length, in characters, of any message sent to the debug sidebar tab */ 434 | debugMaxLength: 1000, 435 | 436 | /** Maximum buffer size for the exec node. Defaults to 10Mb */ 437 | //execMaxBufferSize: 10000000, 438 | 439 | /** Timeout in milliseconds for HTTP request connections. Defaults to 120s */ 440 | //httpRequestTimeout: 120000, 441 | 442 | /** Retry time in milliseconds for MQTT connections */ 443 | mqttReconnectTime: 15000, 444 | 445 | /** Retry time in milliseconds for Serial port connections */ 446 | serialReconnectTime: 15000, 447 | 448 | /** Retry time in milliseconds for TCP socket connections */ 449 | //socketReconnectTime: 10000, 450 | 451 | /** Timeout in milliseconds for TCP server socket connections. Defaults to no timeout */ 452 | //socketTimeout: 120000, 453 | 454 | /** Maximum number of messages to wait in queue while attempting to connect to TCP socket 455 | * defaults to 1000 456 | */ 457 | //tcpMsgQueueSize: 2000, 458 | 459 | /** Timeout in milliseconds for inbound WebSocket connections that do not 460 | * match any configured node. Defaults to 5000 461 | */ 462 | //inboundWebSocketTimeout: 5000, 463 | 464 | /** To disable the option for using local files for storing keys and 465 | * certificates in the TLS configuration node, set this to true. 466 | */ 467 | //tlsConfigDisableLocalFiles: true, 468 | 469 | /** The following property can be used to verify websocket connection attempts. 470 | * This allows, for example, the HTTP request headers to be checked to ensure 471 | * they include valid authentication information. 472 | */ 473 | //webSocketNodeVerifyClient: function(info) { 474 | // /** 'info' has three properties: 475 | // * - origin : the value in the Origin header 476 | // * - req : the HTTP request 477 | // * - secure : true if req.connection.authorized or req.connection.encrypted is set 478 | // * 479 | // * The function should return true if the connection should be accepted, false otherwise. 480 | // * 481 | // * Alternatively, if this function is defined to accept a second argument, callback, 482 | // * it can be used to verify the client asynchronously. 483 | // * The callback takes three arguments: 484 | // * - result : boolean, whether to accept the connection or not 485 | // * - code : if result is false, the HTTP error status to return 486 | // * - reason: if result is false, the HTTP reason string to return 487 | // */ 488 | //}, 489 | } 490 | -------------------------------------------------------------------------------- /update/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "mini": { 3 | "1": { 4 | "src": "mini/fla.py", 5 | "dst": "/home/pi/OpenScan/files/fla.py", 6 | "filesize": 3910 7 | }, 8 | "2": { 9 | "src": "mini/Arducam.py", 10 | "dst": "/usr/lib/python3/dist-packages/Arducam.py", 11 | "filesize": 6154 12 | }, 13 | "3": { 14 | "src": "mini/OpenScan.py", 15 | "dst": "/usr/lib/python3/dist-packages/OpenScan.py", 16 | "filesize": 5864 17 | }, 18 | "4": { 19 | "src": "mini/config.txt", 20 | "dst": "/boot/config.txt", 21 | "filesize": 2196 22 | }, 23 | "5": { 24 | "src": "mini/flows.json", 25 | "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", 26 | "filesize": 178895 27 | }, 28 | "6": { 29 | "src": "mini/settings.js", 30 | "dst": "/root/.node-red/settings.js", 31 | "filesize": 20321 32 | }, 33 | "7": { 34 | "src": "files/logo2.jpg", 35 | "dst": "/home/pi/OpenScan/files/logo.jpg", 36 | "filesize": 582519 37 | } 38 | }, 39 | "betaArdu": { 40 | "1": { 41 | "src": "betaArdu/fla.py", 42 | "dst": "/home/pi/OpenScan/files/fla.py", 43 | "filesize": 12089 44 | }, 45 | "2": { 46 | "src": "betaArdu/OpenScan.py", 47 | "dst": "/usr/lib/python3/dist-packages/OpenScan.py", 48 | "filesize": 10253 49 | }, 50 | "3": { 51 | "src": "betaArdu/config.txt", 52 | "dst": "/boot/config.txt", 53 | "filesize": 2185 54 | }, 55 | "4": { 56 | "src": "betaArdu/flows.json", 57 | "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", 58 | "filesize": 315147 59 | }, 60 | "5": { 61 | "src": "betaArdu/settings.js", 62 | "dst": "/root/.node-red/settings.js", 63 | "filesize": 21199 64 | } 65 | }, 66 | "main": { 67 | "1": { 68 | "src": "main/fla.py", 69 | "dst": "/home/pi/OpenScan/files/fla.py", 70 | "filesize": 5445 71 | }, 72 | "2": { 73 | "src": "main/Arducam.py", 74 | "dst": "/usr/lib/python3/dist-packages/Arducam.py", 75 | "filesize": 6154 76 | }, 77 | "3": { 78 | "src": "main/OpenScan.py", 79 | "dst": "/usr/lib/python3/dist-packages/OpenScan.py", 80 | "filesize": 6161 81 | }, 82 | "4": { 83 | "src": "main/config.txt", 84 | "dst": "/boot/config.txt", 85 | "filesize": 2179 86 | }, 87 | "5": { 88 | "src": "main/flows.json", 89 | "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", 90 | "filesize": 360661 91 | }, 92 | "6": { 93 | "src": "main/settings.js", 94 | "dst": "/root/.node-red/settings.js", 95 | "filesize": 20321 96 | }, 97 | "7": { 98 | "src": "files/logo.jpg", 99 | "dst": "/home/pi/OpenScan/files/logo.jpg", 100 | "filesize": 582519 101 | } 102 | }, 103 | "beta": { 104 | "1": { 105 | "src": "beta/fla.py", 106 | "dst": "/home/pi/OpenScan/files/fla.py", 107 | "filesize": 5366 108 | }, 109 | "2": { 110 | "src": "beta/Arducam.py", 111 | "dst": "/usr/lib/python3/dist-packages/Arducam.py", 112 | "filesize": 6154 113 | }, 114 | "3": { 115 | "src": "beta/OpenScan.py", 116 | "dst": "/usr/lib/python3/dist-packages/OpenScan.py", 117 | "filesize": 6443 118 | }, 119 | "4": { 120 | "src": "beta/config.txt", 121 | "dst": "/boot/config.txt", 122 | "filesize": 2179 123 | }, 124 | "5": { 125 | "src": "beta/flows.json", 126 | "dst": "/home/pi/OpenScan/settings/.node-red/flows.json", 127 | "filesize": 354569 128 | }, 129 | "6": { 130 | "src": "beta/settings.js", 131 | "dst": "/root/.node-red/settings.js", 132 | "filesize": 20321 133 | }, 134 | "7": { 135 | "src": "files/logo.jpg", 136 | "dst": "/home/pi/OpenScan/files/logo.jpg", 137 | "filesize": 582519 138 | } 139 | } 140 | } --------------------------------------------------------------------------------