bluer-sbc 8.233.1__py3-none-any.whl → 9.31.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bluer-sbc might be problematic. Click here for more details.

Files changed (44) hide show
  1. bluer_sbc/.abcli/{blue_sbc.sh → bluer_sbc.sh} +2 -0
  2. bluer_sbc/.abcli/sbc/parts/adjust.sh +12 -0
  3. bluer_sbc/.abcli/sbc/parts/cd.sh +5 -0
  4. bluer_sbc/.abcli/sbc/parts.sh +15 -0
  5. bluer_sbc/.abcli/tests/help.sh +4 -0
  6. bluer_sbc/.abcli/tests/parts_adjust.sh +13 -0
  7. bluer_sbc/README/__init__.py +0 -0
  8. bluer_sbc/README/aliases.py +11 -0
  9. bluer_sbc/README/build.py +29 -0
  10. bluer_sbc/README/design.py +32 -0
  11. bluer_sbc/README/designs/__init__.py +54 -0
  12. bluer_sbc/README/designs/battery_bus.py +26 -0
  13. bluer_sbc/{designs → README/designs}/bryce.py +1 -1
  14. bluer_sbc/{designs → README/designs}/cheshmak.py +1 -1
  15. bluer_sbc/{designs → README/designs}/nafha.py +1 -1
  16. bluer_sbc/{designs → README/designs}/shelter.py +3 -3
  17. bluer_sbc/{designs → README/designs}/swallow.py +1 -1
  18. bluer_sbc/{designs → README/designs}/swallow_head.py +2 -2
  19. bluer_sbc/{designs → README/designs}/ultrasonic_sensor_tester.py +1 -1
  20. bluer_sbc/README/designs/x.py +26 -0
  21. bluer_sbc/README/parts.py +15 -0
  22. bluer_sbc/README/root.py +30 -0
  23. bluer_sbc/README/shortcuts.py +17 -0
  24. bluer_sbc/__init__.py +2 -2
  25. bluer_sbc/__main__.py +3 -2
  26. bluer_sbc/designs/swallow/__init__.py +0 -0
  27. bluer_sbc/designs/swallow/parts.py +3 -0
  28. bluer_sbc/designs/swallow_head/__init__.py +0 -0
  29. bluer_sbc/designs/swallow_head/parts.py +15 -0
  30. bluer_sbc/help/functions.py +2 -0
  31. bluer_sbc/help/parts.py +49 -0
  32. bluer_sbc/parts/__init__.py +0 -0
  33. bluer_sbc/parts/__main__.py +41 -0
  34. bluer_sbc/parts/classes/db.py +208 -0
  35. bluer_sbc/parts/classes/part.py +96 -0
  36. bluer_sbc/parts/db.py +381 -0
  37. bluer_sbc-9.31.1.dist-info/METADATA +72 -0
  38. {bluer_sbc-8.233.1.dist-info → bluer_sbc-9.31.1.dist-info}/RECORD +43 -19
  39. bluer_sbc-8.233.1.dist-info/METADATA +0 -64
  40. /bluer_sbc/{designs → README/designs}/blue_bracket.py +0 -0
  41. /bluer_sbc/{designs → README/designs}/consts.py +0 -0
  42. {bluer_sbc-8.233.1.dist-info → bluer_sbc-9.31.1.dist-info}/WHEEL +0 -0
  43. {bluer_sbc-8.233.1.dist-info → bluer_sbc-9.31.1.dist-info}/licenses/LICENSE +0 -0
  44. {bluer_sbc-8.233.1.dist-info → bluer_sbc-9.31.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,208 @@
1
+ from typing import Dict, List, Iterator, Tuple
2
+ import copy
3
+ import os
4
+ import numpy as np
5
+ import cv2
6
+ from functools import reduce
7
+ from tqdm import tqdm
8
+
9
+ from blueness import module
10
+ from bluer_options.logger import log_list
11
+ from bluer_objects import file
12
+ from bluer_objects import README
13
+ from bluer_objects.README.consts import assets_path, assets_url
14
+
15
+ from bluer_sbc import NAME
16
+ from bluer_sbc.parts.classes.part import Part
17
+ from bluer_sbc.logger import logger
18
+
19
+ NAME = module.name(__file__, NAME)
20
+
21
+
22
+ class PartDB:
23
+ def __init__(self):
24
+ self._db: Dict[str, Part] = {}
25
+
26
+ suffix = "bluer-sbc/parts"
27
+ self.url_prefix = assets_url(
28
+ suffix=suffix,
29
+ volume=2,
30
+ )
31
+ self.path = assets_path(
32
+ suffix=suffix,
33
+ volume=2,
34
+ )
35
+
36
+ def __iter__(self):
37
+ return iter(self._db.values())
38
+
39
+ def __setitem__(
40
+ self,
41
+ name: str,
42
+ part: Part,
43
+ ):
44
+ assert isinstance(part, Part)
45
+
46
+ self._db[name] = copy.deepcopy(part)
47
+ self._db[name].name = name
48
+
49
+ def __getitem__(self, name: str) -> Part:
50
+ return self._db[name]
51
+
52
+ def items(self) -> Iterator[Tuple[str, Part]]:
53
+ return self._db.items()
54
+
55
+ @property
56
+ def README(self) -> List[str]:
57
+ return sorted(
58
+ [
59
+ "- [{}](./{}.md).".format(
60
+ part.info[0],
61
+ part.name,
62
+ )
63
+ for part in self
64
+ ]
65
+ )
66
+
67
+ def adjust(
68
+ self,
69
+ dryrun: bool = True,
70
+ verbose: bool = False,
71
+ ) -> bool:
72
+ logger.info(
73
+ "{}.adjust{}".format(
74
+ NAME,
75
+ " [dryrun]" if dryrun else "",
76
+ )
77
+ )
78
+
79
+ list_of_filenames = reduce(
80
+ lambda x, y: x + y,
81
+ [
82
+ [part.images[0]]
83
+ for part_name, part in self._db.items()
84
+ if part_name != "template" and part.images
85
+ ],
86
+ [],
87
+ )
88
+ log_list(logger, "adjusting", list_of_filenames, "images")
89
+
90
+ max_width = 0
91
+ max_height = 0
92
+ for filename in tqdm(list_of_filenames):
93
+ success, image = file.load_image(
94
+ os.path.join(self.path, filename),
95
+ log=verbose,
96
+ )
97
+ if not success:
98
+ return success
99
+
100
+ max_height = max(max_height, image.shape[0])
101
+ max_width = max(max_width, image.shape[1])
102
+
103
+ logger.info(f"size: {max_height} x {max_width}")
104
+
105
+ for filename in tqdm(list_of_filenames):
106
+ success, image = file.load_image(
107
+ os.path.join(self.path, filename),
108
+ log=verbose,
109
+ )
110
+ if not success:
111
+ return success
112
+
113
+ if image.shape[0] == max_height and image.shape[1] == max_width:
114
+ logger.info("✅")
115
+ continue
116
+
117
+ image = image[:, :, :3]
118
+
119
+ scale = min(
120
+ max_height / image.shape[0],
121
+ max_width / image.shape[1],
122
+ )
123
+ image = cv2.resize(
124
+ image,
125
+ dsize=(
126
+ int(scale * image.shape[1]),
127
+ int(scale * image.shape[0]),
128
+ ),
129
+ interpolation=cv2.INTER_LINEAR,
130
+ )
131
+
132
+ padded_image = (
133
+ np.ones(
134
+ (max_height, max_width, 3),
135
+ dtype=np.uint8,
136
+ )
137
+ * 255
138
+ )
139
+
140
+ y_offset = (max_height - image.shape[0]) // 2
141
+ x_offset = (max_width - image.shape[1]) // 2
142
+
143
+ padded_image[
144
+ y_offset : y_offset + image.shape[0],
145
+ x_offset : x_offset + image.shape[1],
146
+ ] = image
147
+
148
+ if not dryrun:
149
+ if not file.save_image(
150
+ os.path.join(self.path, filename),
151
+ padded_image,
152
+ log=verbose,
153
+ ):
154
+ return False
155
+
156
+ return True
157
+
158
+ def as_images(
159
+ self,
160
+ dict_of_parts: Dict[str, str],
161
+ reference: str = "../../parts",
162
+ ) -> List[str]:
163
+ return README.Items(
164
+ [
165
+ {
166
+ "name": self._db[part_name].info[0],
167
+ "marquee": self._db[part_name].image_url(
168
+ url_prefix=self.url_prefix
169
+ ),
170
+ "description": description,
171
+ "url": f"{reference}/{part_name}.md",
172
+ }
173
+ for part_name, description in dict_of_parts.items()
174
+ ],
175
+ sort=True,
176
+ )
177
+
178
+ def as_list(
179
+ self,
180
+ dict_of_parts: Dict[str, str],
181
+ reference: str = "../../parts",
182
+ log: bool = True,
183
+ ) -> List[str]:
184
+ if log:
185
+ logger.info(
186
+ "{}.as_list: {}".format(
187
+ self.__class__.__name__,
188
+ ", ".join(dict_of_parts.keys()),
189
+ )
190
+ )
191
+
192
+ for part_name in dict_of_parts:
193
+ if part_name not in self._db:
194
+ logger.error(f"{part_name}: part not found.")
195
+ assert False
196
+
197
+ return sorted(
198
+ [
199
+ (
200
+ "1. [{}{}]({}).".format(
201
+ self._db[part_name].info[0],
202
+ ": {}".format(description) if description else "",
203
+ f"{reference}/{part_name}.md",
204
+ )
205
+ )
206
+ for part_name, description in dict_of_parts.items()
207
+ ]
208
+ )
@@ -0,0 +1,96 @@
1
+ from typing import List, Union
2
+ import copy
3
+
4
+ from bluer_objects import markdown
5
+ from bluer_objects import file
6
+
7
+
8
+ class Part:
9
+ def __init__(
10
+ self,
11
+ info: Union[List[str], str] = [],
12
+ name: str = "",
13
+ images: List[str] = [],
14
+ ):
15
+ self.name = name
16
+
17
+ self.info = (
18
+ copy.deepcopy(info)
19
+ if isinstance(
20
+ info,
21
+ list,
22
+ )
23
+ else [info]
24
+ )
25
+
26
+ self.images = (
27
+ copy.deepcopy(images)
28
+ if isinstance(
29
+ images,
30
+ list,
31
+ )
32
+ else [images]
33
+ )
34
+
35
+ def filename(
36
+ self,
37
+ create: bool = False,
38
+ ) -> str:
39
+ reference = file.path(__file__)
40
+ filename = file.absolute(
41
+ f"../../docs/parts/{self.name}.md",
42
+ reference,
43
+ )
44
+
45
+ if not create or file.exists(filename):
46
+ return filename
47
+
48
+ template_filename = file.absolute(
49
+ "../../docs/parts/template-template.md",
50
+ reference,
51
+ )
52
+ assert file.copy(
53
+ template_filename,
54
+ file.add_suffix(filename, "template"),
55
+ log=True,
56
+ ), template_filename
57
+
58
+ return filename
59
+
60
+ def image_url(
61
+ self,
62
+ url_prefix: str,
63
+ filename: str = "",
64
+ ) -> str:
65
+
66
+ return (
67
+ "{}/{}?raw=true".format(
68
+ url_prefix,
69
+ filename if filename else self.images[0],
70
+ )
71
+ if self.images
72
+ else ""
73
+ )
74
+
75
+ def README(
76
+ self,
77
+ url_prefix: str,
78
+ ) -> List[str]:
79
+ return [f"- {info}" for info in self.info] + (
80
+ [""]
81
+ + markdown.generate_table(
82
+ [
83
+ "![image]({})".format(
84
+ self.image_url(
85
+ url_prefix,
86
+ filename,
87
+ )
88
+ )
89
+ for filename in self.images
90
+ ],
91
+ cols=3,
92
+ log=False,
93
+ )
94
+ if self.images
95
+ else []
96
+ )
bluer_sbc/parts/db.py ADDED
@@ -0,0 +1,381 @@
1
+ from bluer_sbc.parts.classes.part import Part
2
+ from bluer_sbc.parts.classes.db import PartDB
3
+
4
+ db_of_parts: PartDB = PartDB()
5
+
6
+ db_of_parts["resistor"] = Part(
7
+ info=[
8
+ "Resistor, 1/4 watt, 5% tolerance",
9
+ ],
10
+ images=["resistor.png"],
11
+ )
12
+
13
+ db_of_parts["4-ch-transceiver"] = Part(
14
+ info=[
15
+ "4-channel transmitter and receiver",
16
+ "source: [digikala](https://www.digikala.com/product/dkp-11037586/%DA%AF%DB%8C%D8%B1%D9%86%D8%AF%D9%87-%D9%88-%D9%81%D8%B1%D8%B3%D8%AA%D9%86%D8%AF%D9%87-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%DA%A9%D9%86%D8%AA%D8%B1%D9%84%DB%8C-%D9%85%D8%AF%D9%84-4ch-led/)",
17
+ "voltages: receiver 6 VDC, transmitter 3 VDC",
18
+ ],
19
+ images="4-channel-remote-control.png",
20
+ )
21
+
22
+ db_of_parts["470-mF"] = Part(
23
+ info=[
24
+ "capacitor, 470 μF to 1000 μF, 16 V or 25 V, Electrolytic, 105 °C rated if possible."
25
+ ],
26
+ images=["capacitor.png"],
27
+ )
28
+
29
+ db_of_parts["BTS7960"] = Part(
30
+ info=[
31
+ "43 A, H-Bridge Motor Driver",
32
+ "specs: [BTS7960](https://www.handsontec.com/dataspecs/module/BTS7960%20Motor%20Driver.pdf)",
33
+ ],
34
+ images="bts7960.jpg",
35
+ )
36
+
37
+ db_of_parts["dc-motor-12-VDC-45W"] = Part(
38
+ info=[
39
+ "12 VDC motor, 20-45 W",
40
+ "type 1: 9,000 RPM, output ~60 RPM",
41
+ "type 2: 10,000 RPM, output 72 RPM",
42
+ "https://parsbike.com/product/%D9%85%D9%88%D8%AA%D9%88%D8%B1-%DA%AF%DB%8C%D8%B1%D8%A8%DA%A9%D8%B3-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D8%B4%D8%A7%D8%B1%DA%98%DB%8C-%D9%88-%D9%85%D9%88%D8%AA%D9%88%D8%B1-%D8%B4%D8%A7%D8%B1%DA%98%DB%8C/",
43
+ ],
44
+ images=[
45
+ "gearbox1.jpg",
46
+ "gearbox2.jpg",
47
+ "gearbox3.jpg",
48
+ "gearbox4.jpg",
49
+ "gearbox5.jpg",
50
+ "gearbox6.jpg",
51
+ "gearbox7.jpg",
52
+ "gearbox8.jpg",
53
+ ],
54
+ )
55
+
56
+ db_of_parts["LED"] = Part(
57
+ info=[
58
+ "LED, ~2 V forward voltage, 10-20 mA",
59
+ ],
60
+ images=["led.png"],
61
+ )
62
+
63
+ db_of_parts["Polyfuse"] = Part(
64
+ info=[
65
+ "Polyfuse, 1.1 A hold, 2.2 A trip, 16 V, resettable, through-hole, e.g., MF-R110",
66
+ ],
67
+ images=["polyfuse.png"],
68
+ )
69
+
70
+ db_of_parts["rpi3bp"] = Part(
71
+ info=[
72
+ "Raspberry Pi 3B+",
73
+ ],
74
+ images=[
75
+ "rpi3bplus.png",
76
+ "gpio-pinout.png",
77
+ ],
78
+ )
79
+
80
+ db_of_parts["SLA-Battery"] = Part(
81
+ info=[
82
+ "Rechargeable sealed lead acid battery, 12 V, 7 Ah",
83
+ ],
84
+ images=[
85
+ "battery.png",
86
+ ],
87
+ )
88
+
89
+ db_of_parts["TVS-diode"] = Part(
90
+ info=[
91
+ "TVS diode, unidirectional, 600 W, 6.8 V clamp, e.g. P6KE6.8A, DO-15 package",
92
+ ],
93
+ images=[
94
+ "TVSdiode.png",
95
+ ],
96
+ )
97
+
98
+ db_of_parts["XL4015"] = Part(
99
+ info=[
100
+ "XL4015: 12 VDC -> 5 VDC, 4A",
101
+ "specs: [XL4015](https://www.handsontec.com/dataspecs/module/XL4015-5A-PS.pdf)",
102
+ ],
103
+ images=[
104
+ "XL4015.png",
105
+ ],
106
+ )
107
+
108
+ db_of_parts["rpi-camera"] = Part(
109
+ info=[
110
+ "Raspberry Pi Camera, V1.3"
111
+ "https://www.raspberrypi.com/documentation/accessories/camera.html",
112
+ ],
113
+ images=[
114
+ "rpi-camera.jpg",
115
+ ],
116
+ )
117
+
118
+ db_of_parts["DC-gearboxed-motor-12V-120RPM"] = Part(
119
+ info=[
120
+ "Gearboxed DC Motor, 12 V (3-24 V), 3A, 120 RPM, 1:91, 15 Kg cm",
121
+ "[GM6558](https://www.landaelectronic.com/product/%d9%85%d9%88%d8%aa%d9%88%d8%b1-dc-%da%af%db%8c%d8%b1%d8%a8%da%a9%d8%b3-%d8%ad%d9%84%d8%b2%d9%88%d9%86%db%8c-gm6558/)",
122
+ ],
123
+ images=[
124
+ "GM6558/01.jpg",
125
+ "GM6558/02.jpg",
126
+ "GM6558/03.jpg",
127
+ "GM6558/04.jpg",
128
+ "GM6558/measurements.jpg",
129
+ "GM6558/specs.png",
130
+ ],
131
+ )
132
+
133
+ db_of_parts["2xAA-battery-holder"] = Part(
134
+ info=[
135
+ "2 x AA battery holder",
136
+ ],
137
+ images=[
138
+ "2xAA-battery-holder.jpg",
139
+ ],
140
+ )
141
+
142
+ db_of_parts["4xAA-battery-holder"] = Part(
143
+ info=[
144
+ "4 x AA battery holder",
145
+ ],
146
+ images=[
147
+ "4xAA-battery-holder.jpg",
148
+ ],
149
+ )
150
+
151
+ db_of_parts["PCB-double-9x7"] = Part(
152
+ info=[
153
+ "double-sided PCB, 9 cm x 7 cm",
154
+ ],
155
+ images=[
156
+ "PCB-double-9x7.jpeg",
157
+ ],
158
+ )
159
+
160
+ db_of_parts["PCB-single-14x9_5"] = Part(
161
+ info=[
162
+ "single-sided PCB, 14 cm x 9.5 cm",
163
+ ],
164
+ images=[
165
+ "pcb-14x9_5cm.jpg",
166
+ ],
167
+ )
168
+
169
+ db_of_parts["pushbutton"] = Part(
170
+ info=[
171
+ "push button",
172
+ ],
173
+ images=[
174
+ "pushbutton.png",
175
+ ],
176
+ )
177
+
178
+ db_of_parts["yellow-gearbox-dc-motor"] = Part(
179
+ info=[
180
+ "gearboxed DC motor, 6V DC",
181
+ ],
182
+ images=[
183
+ "yellow-gearbox-dc-motor.png",
184
+ ],
185
+ )
186
+
187
+ db_of_parts["yellow-wheels"] = Part(
188
+ info=[
189
+ "wheels for gearboxed DC motor",
190
+ ],
191
+ images=[
192
+ "yellow-wheels.jpg",
193
+ ],
194
+ )
195
+
196
+ db_of_parts["36v-hub-motor"] = Part(
197
+ info=[
198
+ "36V DC hub motor, 350 W, front, no gearbox",
199
+ "[source](https://samamotor.ir/%D9%87%D8%A7%D8%A8-%D9%85%D9%88%D8%AA%D9%88%D8%B1/5105-%D9%87%D8%A7%D8%A8-%D9%85%D9%88%D8%AA%D9%88%D8%B1-350-%D9%88%D8%A7%D8%AA-36-%D9%88%D9%84%D8%AA-%D8%A8%D8%AF%D9%88%D9%86-%DA%AF%DB%8C%D8%B1%D8%A8%DA%A9%D8%B3-%D8%AF%D9%88%DA%86%D8%B1%D8%AE%D9%87-%D9%85%D8%AE%D8%B5%D9%88%D8%B5-%DA%86%D8%B1%D8%AE-%D8%AC%D9%84%D9%88-.html)",
200
+ ],
201
+ images=[
202
+ "36v-hub-motor.jpg",
203
+ ],
204
+ )
205
+
206
+ db_of_parts["brushless-350w-drive"] = Part(
207
+ info=[
208
+ "brushless drive, 36 - 48 V DC, 350 W, sine wave, silent",
209
+ "[source](https://samamotor.ir/%D8%AF%D8%B1%D8%A7%DB%8C%D9%88%D8%B1-%D9%85%D9%88%D8%AA%D9%88%D8%B1-%D8%A8%D8%B1%D8%A7%D8%B4%D9%84%D8%B3-bldc/4821-%D8%AF%D8%B1%D8%A7%DB%8C%D9%88%D8%B1-%D8%A8%D8%B1%D8%A7%D8%B4%D9%84%D8%B3-36-48-%D9%88%D9%84%D8%AA-350-%D9%88%D8%A7%D8%AA-sine-wave-silent.html)",
210
+ ],
211
+ images=[
212
+ "brushless-350w-drive.jpg",
213
+ ],
214
+ )
215
+
216
+ db_of_parts["LJ-6V-battery"] = Part(
217
+ info=[
218
+ "6V DC (4 cell) NICD battery",
219
+ "https://www.digikala.com/product/dkp-3213588/%C3%98/",
220
+ ],
221
+ images=[
222
+ "LJ-6V-battery.jpg",
223
+ ],
224
+ )
225
+
226
+
227
+ db_of_parts["USB-charger-NICD-6V"] = Part(
228
+ info=[
229
+ "6V DC charger for NICD batteries",
230
+ "https://www.digikala.com/product/dkp-5977954/%D8%B4%D8%A7%D8%B1%DA%98%D8%B1-%D8%A8%D8%A7%D8%AA%D8%B1%DB%8C-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%DA%A9%D9%86%D8%AA%D8%B1%D9%84%DB%8C-%D9%85%D8%AF%D9%84-6-%D9%88%D9%84%D8%AA-%DA%A9%D8%AF-6v-usb-sm-%D8%A8%D9%87-%D9%87%D9%85%D8%B1%D8%A7%D9%87-%D8%B3%D9%88%DA%A9%D8%AA-sm-%D8%AF%D9%88-%D9%BE%DB%8C%D9%86/",
231
+ ],
232
+ images=[
233
+ "USB-charger-NICD-6V-1.jpg",
234
+ "USB-charger-NICD-6V-2.jpg",
235
+ ],
236
+ )
237
+
238
+ db_of_parts["L-1x2"] = Part(
239
+ info=[
240
+ "L 1x2",
241
+ "https://robotexiran.com/product/%d8%a8%d8%b3%d8%aa-21-l/",
242
+ ],
243
+ images=[
244
+ "L-1x2-1.jpg",
245
+ "L-1x2-2.jpg",
246
+ ],
247
+ )
248
+
249
+ db_of_parts["shaft-10cm"] = Part(
250
+ info=[
251
+ "shaft, 10 cm",
252
+ "https://robotexiran.com/product/%d9%85%d8%ad%d9%88%d8%b1-10cm/",
253
+ ],
254
+ images=[
255
+ "shaft-10cm.jpg",
256
+ ],
257
+ )
258
+
259
+ db_of_parts["M3"] = Part(
260
+ info=[
261
+ "M3 nuts and bolts",
262
+ ],
263
+ images=[
264
+ "M3.jpg",
265
+ ],
266
+ )
267
+
268
+ db_of_parts["front-connector"] = Part(
269
+ info=[
270
+ "front connector",
271
+ ],
272
+ images=[
273
+ "front-connector.jpg",
274
+ ],
275
+ )
276
+
277
+ db_of_parts["front-wheels"] = Part(
278
+ info=[
279
+ "front wheels",
280
+ ],
281
+ images=[
282
+ "front-wheels.jpg",
283
+ ],
284
+ )
285
+
286
+ db_of_parts["wheel"] = Part(
287
+ info=[
288
+ "power wheel wheels",
289
+ "https://sarobatic.ir/product/%DA%86%D8%B1%D8%AE-%D8%A8%D8%B2%D8%B1%DA%AF-%D8%B9%D9%82%D8%A8-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D8%B4%D8%A7%D8%B1%DA%98%DB%8C-%D8%A7%D8%B3%D8%AA%D9%88%DA%A9/",
290
+ "https://toys-repair.ir/product/2768/",
291
+ ],
292
+ images=[
293
+ "wheel1.jpg",
294
+ "wheel4.jpg",
295
+ "wheel3.jpg",
296
+ ],
297
+ )
298
+
299
+ db_of_parts["ultrasonic-sensor"] = Part(
300
+ info=[
301
+ "HC-SR04: ultrasonic-sensor",
302
+ "[datasheet](https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf)",
303
+ "1m ~= 6ms",
304
+ "fov = 15 - 30 deg",
305
+ ],
306
+ images=[
307
+ "HC-SR04.jpg",
308
+ ],
309
+ )
310
+
311
+ db_of_parts["connector"] = Part(
312
+ info=[
313
+ "auto power connectors",
314
+ ],
315
+ images=[
316
+ "connector.jpg",
317
+ ],
318
+ )
319
+
320
+ db_of_parts["1N4148"] = Part(
321
+ info=[
322
+ "1N4148 diode",
323
+ ],
324
+ images=[
325
+ "TVSdiode.png",
326
+ ],
327
+ )
328
+
329
+ db_of_parts["40-inch-TV"] = Part(
330
+ info=[
331
+ "40 inch TV",
332
+ ],
333
+ images=[
334
+ "40-inch-TV.jpg",
335
+ ],
336
+ )
337
+
338
+ db_of_parts["power-station"] = Part(
339
+ info=[
340
+ "power station.",
341
+ ],
342
+ images=[
343
+ "power-station.jpeg",
344
+ ],
345
+ )
346
+
347
+ db_of_parts["TV-bracket"] = Part(
348
+ info=[
349
+ "TV bracket",
350
+ ],
351
+ images=[
352
+ "tv-bracket.jpeg",
353
+ ],
354
+ )
355
+
356
+ db_of_parts["on-off-switch"] = Part(
357
+ info=[
358
+ "on/off switch",
359
+ ],
360
+ images=[
361
+ "on-off-switch.png",
362
+ ],
363
+ )
364
+
365
+ db_of_parts["charging-port"] = Part(
366
+ info=[
367
+ "charging port",
368
+ ],
369
+ images=[
370
+ "charging-port.jpg",
371
+ ],
372
+ )
373
+
374
+ db_of_parts["template"] = Part(
375
+ info=[
376
+ "template",
377
+ ],
378
+ images=[
379
+ "template.jpg",
380
+ ],
381
+ )