├── .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 | }
--------------------------------------------------------------------------------