Compare commits

...
This repository has been archived on 2025-06-01. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

261 commits

Author SHA1 Message Date
Matthias Guzmits
36d4379ddc Merge branch 'master' of https://github.com/F-WuTS/compLIB 2023-07-19 19:18:16 +02:00
Matthias Guzmits
c593059fb1 Add button functionality 2023-07-19 19:17:59 +02:00
Matthias Guzmits
ed04957d94 Add drive arc functionality 2023-07-19 18:36:07 +02:00
itssme
27962d16a7
(MAJOR) Bump to new major version after deleting invalid tag 2023-07-18 09:18:33 +02:00
itssme
9c40daae88
(MAJOR) Bump to new major version 2023-07-18 09:17:25 +02:00
Matthias Guzmits
de112d98e4 Added basic drive functions 2023-07-18 00:44:34 +02:00
Joel Klimont
fa4cc1a0ad fixed syntax error in postinstallscript 2023-02-10 14:29:13 +01:00
Joel Klimont
7e2977a653 fixed syntax error in postinstallscript 2023-02-10 14:02:07 +01:00
Joel Klimont
cf415d38e8 fixed documentation 2023-02-10 13:44:28 +01:00
Joel Klimont
f09d20ade2 removed .idea folder from repo 2023-01-14 15:11:57 +01:00
Joel Klimont
671292a6cf fixed moving built package to the correct output directory 2022-12-18 15:08:20 +01:00
itssme
b95b9bf518
(MINOR) updated readme 2022-12-18 14:00:11 +01:00
Konstantin Lampalzer
dcef53712f fix keyerror setup py 2022-12-18 00:03:26 +01:00
Konstantin Lampalzer
c02cfcd71c Move client foler 2022-12-17 23:59:06 +01:00
Konstantin Lampalzer
4c24717278 Skip posinstall on x86_64 2022-12-17 23:40:58 +01:00
Joel Klimont
19d98f1f04 nginx server is now stopped, as it is not needed anymore 2022-11-24 15:32:15 +01:00
Joel Klimont
13eefdceb1 fixed a bug where the postinstall script would fail, if "raspi-config" is not installed on the system 2022-11-19 01:42:11 +01:00
Joel Klimont
f03df1b3b3 updated opencv documentation (camera module)
added enabling of legacy support for the camera module to the postinstall script
2022-11-18 17:19:40 +01:00
Konstantin Lampalzer
ee4f6a516d Update faq 2022-11-18 15:47:47 +01:00
Konstantin Lampalzer
9e589fd681 Change motor speed to cm/s 2022-11-13 02:52:30 +01:00
Joel Klimont
be9a5c9f19 added env variable to overwrite seeding seed when in competition mode 2022-11-11 17:47:40 +01:00
Joel Klimont
765336231b added updating instructions to documentation 2022-11-11 17:22:01 +01:00
Joel Klimont
f2724e79f7 added example code from guide to test 2022-11-10 23:14:09 +01:00
Joel Klimont
a5274c2232 function to set random seed and to generate a random number are now private static functions in the Seeding gamestate 2022-11-09 17:07:03 +01:00
Joel Klimont
67947116ec added debug logger in seeding 2022-11-09 15:50:15 +01:00
Joel Klimont
369692b619 added german documentation to seeding and double elimination api/ gamestate 2022-11-09 03:25:27 +01:00
Joel Klimont
de9b671f29 added new Camera class for opencv video processing (currently does not work with Pi-Camera)
changed logging a bit
2022-11-05 21:18:24 +01:00
itssme
b87e830ac3
added missing dependencies 2022-11-05 01:57:58 +01:00
Konstantin Lampalzer
ff2c529d3a Update docs 2022-11-01 23:06:33 +01:00
itssme
e4592e7963
removed compsrv as dependency 2022-10-28 19:23:20 +02:00
Konstantin Lampalzer
285996f467 Update docu 2022-10-28 11:50:41 +02:00
Konstantin Lampalzer
bd0f14a83b Add qa program 2022-10-14 00:35:56 +02:00
Konstantin Lampalzer
1ee4a1e1ef Add linefollower 2022-10-14 00:10:07 +02:00
Konstantin Lampalzer
002db9d650 Add linefollower 2022-10-14 00:08:59 +02:00
Konstantin Lampalzer
f6e45ac25d update documentation 2022-10-13 00:55:54 +02:00
Konstantin Lampalzer
af3aaf7998 update documentation 2022-10-13 00:31:43 +02:00
Konstantin Lampalzer
e1a17808f7 update documentation 2022-10-13 00:02:38 +02:00
Joel Klimont
6245d1308a added OpenCV packages to complib dependencies 2022-10-08 22:26:39 +02:00
Joel Klimont
d7a62cfbf5 (MINOR) changed package architecture to aarch64 (arm64) 2022-10-08 17:31:38 +02:00
Joel Klimont
a84183375b installation path is now manually specified 2022-10-07 19:20:20 +02:00
Joel Klimont
60cfb2669c now specifying python version in fpm build 2022-10-07 19:12:38 +02:00
Joel Klimont
b6c351dd0b github action now builds with python 3.9 instead of 3.8 2022-10-07 18:57:59 +02:00
Konstantin Lampalzer
d4a7d8c0c0 Cleanup 2022-10-07 16:35:37 +02:00
Konstantin Lampalzer
c16a7172e7 Merge branch 'feature/new-architecture' 2022-10-07 16:34:28 +02:00
Konstantin Lampalzer
afe5e9c766 documentation, IP writing service 2022-10-07 01:22:27 +02:00
Joel Klimont
c62b22aa0a (MINOR) clearup in postinstall 2022-10-06 20:54:27 +02:00
Joel Klimont
807ea8e561 changed branch for version number to master 2022-10-06 20:45:33 +02:00
Joel Klimont
8151173d2a fixed version number in complib build 2022-10-06 20:44:16 +02:00
Joel Klimont
16c8f18c06 added building pipeline for complib package 2022-10-06 20:42:08 +02:00
Joel Klimont
03daabaa37 added python3-complib_1.0.0-2_all.deb 2022-10-06 00:04:02 +02:00
Joel Klimont
b2281e2a31 added python3-complib_1.0.0-1_all.deb 2022-10-05 23:57:47 +02:00
Joel Klimont
8d19e876ab added compsrv_1.0.0-3_all.deb 2022-10-05 23:53:08 +02:00
Joel Klimont
41e88c9fd5 added compsrv_1.0.0-2_all.deb 2022-10-05 23:50:36 +02:00
Joel Klimont
587a7503a2 added compsrv_1.0.0-1_all.deb 2022-10-05 23:44:51 +02:00
Konstantin Lampalzer
7a9c4e9f3a Add systemd notify 2022-10-05 23:43:37 +02:00
Joel Klimont
077937f5c6 added dependencies to server package build 2022-10-05 23:28:29 +02:00
Joel Klimont
1117f6efa7 added built package 2022-10-05 23:14:50 +02:00
Konstantin Lampalzer
1d91792c56 Update 2022-10-05 23:13:40 +02:00
Joel Klimont
d1e385a2a1 added env variable for setting a version
small changed in build scripts
2022-10-04 23:00:11 +02:00
Joel Klimont
222d274ea7 fixed compsrv package 2022-10-04 20:40:05 +02:00
Joel Klimont
2dc55fe8e2 added test binary for packaging 2022-10-04 20:30:56 +02:00
Joel Klimont
cc79faf13f started working on compsrv package 2022-10-04 20:28:58 +02:00
Joel Klimont
5b2b7e4ea4 removed pigpio as dependency 2022-10-04 19:01:25 +02:00
Joel Klimont
8ad2d3da8e started adding package building 2022-10-04 18:54:48 +02:00
Joel Klimont
e35b992865 get_material_deliveries test now passes 2022-10-01 20:00:27 +02:00
Joel Klimont
f6b27971c4 added more tests for double elimination and seeding
added eq and nq functions for Position class in de
fixed error in de function that would occur if the game has not started yet
2022-10-01 19:11:04 +02:00
DuSack1220
c325a2c7c2 added unittest for seeding api
added get_material_deliveries to seeding api
started working on de api test
2022-10-01 17:37:24 +02:00
DuSack1220
6946ce9957 added unittest for seeding gamestate generation
fixed bug in gamestate.get_logistic_plan
2022-10-01 16:50:49 +02:00
Joel Klimont
f03b9d51b5 added seeding api 2022-09-30 17:49:42 +02:00
Joel Klimont
52c22995d3 added seeding gamestate 2022-08-28 16:22:03 +02:00
Joel Klimont
72c5af8b56 started working on seeding gamestate and double elimination api 2022-08-28 15:59:11 +02:00
Konstantin Lampalzer
d4c49a0a51 Add movement class to python 2022-06-11 23:52:57 +02:00
Konstantin Lampalzer
2c5f283a46 Fix goToAngle with new odom 2022-06-09 22:44:26 +02:00
80ed5e5bf9 Improvements in odometry kinematics 2022-06-09 10:25:11 +02:00
b3eb01ad6e Improvements in odometry kinematics 2022-06-08 23:42:43 +02:00
4a9367629f Improvements in odometry kinematics 2022-06-08 00:32:19 +02:00
Konstantin Lampalzer
7fe160211f Make compileable on unix not running raspi 2022-06-07 23:53:28 +02:00
Konstantin Lampalzer
37447298a4 Add goToDistance and goToAngle 2022-06-07 22:53:49 +02:00
Konstantin Lampalzer
4ee0b8045f Tune turning a bit 2022-05-29 15:20:11 +02:00
Konstantin Lampalzer
9000316350 add wheel tick calib 2022-05-28 22:25:01 +02:00
Konstantin Lampalzer
4be754c1db Finish drive distance, finish turn_angle 2022-05-28 01:09:34 +02:00
Konstantin Lampalzer
92d42be8b8 Continue on go to goal behaviour 2022-05-27 02:44:52 +02:00
Konstantin Lampalzer
6a1ac72912 Change RPM to M/S 2022-05-26 18:55:57 +02:00
Konstantin Lampalzer
a484bc2137 Add complib client and TCP communication 2022-05-22 23:00:57 +02:00
Konstantin Lampalzer
1a033c8b03 Start on unix socket server on c++ side 2022-05-22 02:30:07 +02:00
Konstantin Lampalzer
0bef6035ae Rework library without unix sockets for now 2022-05-21 23:31:23 +02:00
root
e9ae1a320a Start on v2 2022-05-21 13:10:28 +01:00
root
4d5c26d10c save! 2022-05-16 22:47:22 +01:00
root
1fc400c695 Rework PID 2022-04-02 22:49:04 +01:00
root
5d8df998ae Add motor speed control 2022-03-21 22:37:13 +00:00
root
1a488065ac Add get speed function to motor class 2022-03-21 18:51:16 +00:00
root
8537e8504b Add encoder with filter 2022-03-20 21:21:54 +00:00
Konstantin Lampalzer
5ecf92426a Remove compiled files 2022-03-18 18:18:36 +01:00
Konstantin Lampalzer
ddc550ef1a Add gitignore 2022-03-18 18:18:24 +01:00
Konstantin Lampalzer
50da718472 Add gitignore 2022-03-18 18:17:16 +01:00
Konstantin Lampalzer
9b567b8c6c Protobuf prototype 2022-03-18 18:11:16 +01:00
Joel Klimont
e277a83c5f updated docs 2022-02-16 13:16:04 +01:00
Joel Klimont
a6af166fd1 updated version number to 0.4.1-1 2022-02-14 15:19:39 +01:00
itssme
5d6a4acdf7
Merge pull request #3 from F-WuTS/bugfix/change-health-check-times
Update Spi.py
2022-02-14 15:18:00 +01:00
Konstantin Lampalzer
98fc45e97a
Update Spi.py 2022-02-14 15:10:05 +01:00
Joel Klimont
e1e03933d2 updated version number to 0.4.1-0 2022-02-13 19:15:31 +01:00
itssme
6687d1afba
Merge pull request #2 from F-WuTS/feature/locking
Add Locking
2022-02-13 18:51:38 +01:00
Your Name
0e1b767f26 Fix while true in IPService Lock 2022-02-13 17:50:06 +00:00
Your Name
01cb854f50 Add Locking 2022-02-13 17:30:10 +00:00
Joel Klimont
e7f0f95d66 updated version number to 0.4.0-0
added API_FORCE for overriding API_URL
extensive logging is now disabled by default
2022-02-12 13:16:26 +01:00
Your Name
34e68e2a9e Try another fix 2022-02-11 12:35:48 +00:00
Joel Klimont
2c0f05dd77 updated version number to 0.3.9-0 2022-02-04 18:36:01 +01:00
Konstantin Lampalzer
4b96a1a4a7
Update Motor.py 2022-02-04 18:32:32 +01:00
Joel Klimont
a7de603298 updated version number to 0.3.8-0 2022-02-04 17:51:21 +01:00
root
67d047f521 :Merge branch 'master' of https://github.com/F-WuTS/compLIB 2022-02-04 16:36:59 +00:00
root
2d63fcc58f Fix 3 bugs 2022-02-04 16:36:06 +00:00
Konstantin Lampalzer
df4c672701 Update documentation 2022-01-28 19:53:13 +01:00
Joel Klimont
a5b0933eff updated version number to 0.3.7-0 2022-01-28 19:35:44 +01:00
root
ed9a22b1c7 Merge branch 'master' of https://github.com/F-WuTS/compLIB 2022-01-28 18:34:09 +00:00
root
543be6cdab Fix recursive lock 2022-01-28 18:34:01 +00:00
Joel Klimont
0d15ea1654 updated version number to 0.3.6-0 2022-01-28 19:23:09 +01:00
root
6ba6ab3ae1 Merge branch 'master' of https://github.com/F-WuTS/compLIB 2022-01-28 18:21:36 +00:00
root
2748290e30 Add multiple read to ir sensor 2022-01-28 18:21:15 +00:00
Joel Klimont
d22c91cc4c updated version number to 0.3.5-0 2022-01-28 19:10:03 +01:00
root
fc64706b65 Add locking in spi 2022-01-28 18:09:02 +00:00
Joel Klimont
2b3f6f86c5 updated version number to 0.3.4-0 2022-01-28 16:25:14 +01:00
HerrNamenlos123
e7c82dbc47 hopefully fixed spi read/write errors 2022-01-28 15:43:20 +01:00
Joel Klimont
d4d69daabd updated version number to 0.3.3-0 2022-01-21 16:19:44 +01:00
Konstantin Lampalzer
62f0b96d72
Update Spi.py 2022-01-21 15:50:48 +01:00
Joel Klimont
bbcbfa210f updated version number to 0.3.2-0 2022-01-09 18:48:20 +01:00
Konstantin Lampalzer
866fac9848 Add Documentation for odometrty and robot class. Remove fast_fifo 2022-01-09 18:40:44 +01:00
root
c800b30e31 Add Odometry, Add Robot class, Change SPI Speed, Change Motor to old speeds 2022-01-09 16:48:06 +00:00
itssme
c70ac8fd03
updated readme 2021-12-15 16:49:50 +01:00
Joel Klimont
9b3c8e5b7f added api tests for de
fixed getMeteoroids call in api
2021-12-03 15:28:28 +01:00
root
9e89123e92 New curve, encoder filter 2021-11-19 20:26:57 +00:00
Joel Klimont
d3a26a9539 changes in double elim api for status code 503 2021-11-19 17:08:18 +01:00
Joel Klimont
0db6ed23b2 postinstallscript now enables the background service explicitly 2021-11-19 13:38:34 +01:00
Joel Klimont
7f76813df1 changed version number to 0.3.0-0 2021-11-13 20:43:45 +01:00
root
c5dd14fb1f Add bot driving slower 2021-11-12 19:17:14 +00:00
Joel Klimont
b2d025002e changed version number to 0.2.9-0 2021-11-05 15:23:13 +01:00
HerrNamenlos123
210580d3f1
Update Reset.py 2021-11-05 15:06:20 +01:00
Joel Klimont
4c4da544c2 added basic test for seeding api 2021-10-28 23:23:52 +02:00
Joel Klimont
107ba9c667 updated version 2021-10-16 23:51:59 +02:00
Konstantin Lampalzer
592311d3ee Change lines 2021-10-16 23:38:15 +02:00
Your Name
f7a2a8a261 Add firmware Version to display 2021-10-16 18:18:50 +01:00
Konstantin Lampalzer
222aa46123 Fix docu 2021-10-16 16:36:07 +02:00
Joel Klimont
97b08497d8 updated version 2021-10-14 15:22:09 +02:00
Konstantin Lampalzer
42952eb9e1 Add QC docu, Fix display update 2021-10-14 15:17:35 +02:00
Joel Klimont
d81eb73b46 updated version
fixed ip date output
2021-10-14 14:07:06 +02:00
Joel Klimont
910c5edaee speedup of postinstall script 2021-10-14 14:04:38 +02:00
Joel Klimont
9f09ca3382 updated version number 2021-10-14 14:00:03 +02:00
Konstantin Lampalzer
3f0651abbc herge branch 'master' of github.com:F-WuTS/compLIB 2021-10-14 13:59:27 +02:00
Konstantin Lampalzer
ce7094b5cf Fix spidev 2021-10-14 13:59:20 +02:00
Joel Klimont
74db0ca676 updated version number 2021-10-14 13:44:12 +02:00
Joel Klimont
0f95d4207a stability improvements for ip daemon 2021-10-14 13:37:33 +02:00
Konstantin Lampalzer
eafad7ccdb Add local development to SPI 2021-10-14 13:36:03 +02:00
Joel Klimont
881f8ddbf8 added error output to get_ip 2021-10-06 13:20:21 +02:00
Joel Klimont
436ef35d46 renamed /getMeteoroid to /getMeteoroids
small corrections in ip daemon
2021-10-06 00:19:58 +02:00
Joel Klimont
89d681d10f changed version number to 0.2.5-0 2021-10-02 17:39:48 +02:00
root
f5cc142019 Remove unused print in servo 2021-10-02 12:38:13 +01:00
root
7228af5e40 Fix warning in motor 2021-10-02 11:50:10 +01:00
root
b4eae06eb6 Fix VisionDaemon 2021-10-02 11:45:03 +01:00
itssme
8218e8228e
fixed ip displayer not starting after correct imports 2021-10-02 11:49:33 +02:00
Konstantin Lampalzer
3faee0fb88
Update IRSensor.py 2021-10-01 21:11:52 +02:00
Joel Klimont
d1a86d2c0e fixed logging in background daemon 2021-10-01 13:37:56 +02:00
Joel Klimont
3eab71aaf5 fixed bug in get_meteoroids which would recursively call the wrong function 2021-10-01 02:46:52 +02:00
Joel Klimont
d58a41da69 fixed missing api calls and documentation 2021-10-01 02:40:16 +02:00
Joel Klimont
c515c0062b Merge remote-tracking branch 'origin/master' 2021-10-01 01:50:39 +02:00
Joel Klimont
d06e6fa32d added api requests 2021-10-01 01:50:31 +02:00
Konstantin Lampalzer
023ccb416b Fix documentation for diplay 2021-10-01 01:13:07 +02:00
Joel Klimont
bb4483b141 background daemon now disables heartbeat for itself 2021-10-01 01:04:05 +02:00
Joel Klimont
a81fd4204b fixed version number in package init 2021-10-01 00:33:45 +02:00
Joel Klimont
7f00196680 added ip address writing to daemon 2021-10-01 00:24:08 +02:00
itssme
6eef4e7aa4
added influxdb_client pip package 2021-09-30 16:13:18 +02:00
Konstantin Lampalzer
31eb91d51b Add servo 2021-09-23 20:43:41 +02:00
Konstantin Lampalzer
1c0310b9c7 new linefollower example 2021-09-14 18:34:50 +02:00
Konstantin Lampalzer
3ef0fff256 update documentation 2021-09-13 19:14:47 +02:00
Konstantin Lampalzer
54320096fb Documentation 2021-09-12 13:54:38 +02:00
Konstantin Lampalzer
f5488f7b47 Documentation 2021-09-12 13:46:36 +02:00
Konstantin Lampalzer
cf4e88fd35 Documentation 2021-09-12 13:46:30 +02:00
Konstantin Lampalzer
8f2944da71 Documentation 2021-09-12 13:42:55 +02:00
Konstantin Lampalzer
cbe3a379bd Faster reset 2021-09-12 13:01:37 +02:00
Konstantin Lampalzer
aac99a11ba Minor fixes 2021-09-11 21:12:58 +02:00
Konstantin Lampalzer
3fe3139961 Add MetricsLogging 2021-09-06 18:51:52 +02:00
Konstantin Lampalzer
60f252c37a Fixed sign for motor power 2021-09-05 16:45:30 +01:00
Konstantin Lampalzer
e0600d75c4 Added motor curve linearizer 2021-09-05 14:26:43 +01:00
Konstantin Lampalzer
bdb7d687bb Fixed motor registers 2021-09-05 11:37:16 +01:00
Konstantin Lampalzer
a2425c7010 Merge branch 'master' of https://github.com/F-WuTS/compLIB 2021-09-05 11:27:53 +01:00
Konstantin Lampalzer
50709c96e0 Added prints on library startup 2021-09-05 11:27:23 +01:00
Konstantin Lampalzer
7aa415f860 Merge branch 'master' of github.com:F-WuTS/compLIB 2021-09-05 12:20:29 +02:00
Konstantin Lampalzer
74f0f42d2f Fixed encoder overflow 2021-09-05 11:11:53 +01:00
Konstantin Lampalzer
ce748d8321 Add servo registers 2021-09-04 18:44:56 +02:00
Konstantin Lampalzer
3e7df14da8 Fixed Encoder clear at startup 2021-09-04 15:00:33 +01:00
Konstantin Lampalzer
9e61023e67 Fixed reset delay 2021-09-04 14:50:43 +01:00
Konstantin Lampalzer
91884bd433 Add auto reset of stm on start 2021-09-04 15:35:38 +02:00
HerrNamenlos123
4f564ca759 Fixed SPI error 2021-09-04 05:19:52 -07:00
Konstantin Lampalzer
0f103cb34d Add encoder resetting, SPI health check 2021-08-23 22:13:01 +01:00
Konstantin Lampalzer
63b8f868c9 Add IR, encoder, motor, display 2021-08-22 18:37:20 +02:00
Konstantin Lampalzer
530ddd8be7 Add spi 2021-08-22 13:07:37 +02:00
Joel
4bf031a6c7
small fixes in api requests for double elimination 2021-04-23 19:42:45 +02:00
Konstantin Lampalzer
7431f44804 Add sleep 2021-04-23 19:36:44 +02:00
Konstantin Lampalzer
fca29a7bff Hopefully fix timeouts 2021-04-23 19:32:38 +02:00
HerrNamenlos123
c86d4a350f Added ArUco example 2021-04-05 20:55:07 +01:00
Joel Klimont
bd2000f774 small changes in build script 2021-04-02 18:46:14 +02:00
Joel Klimont
d84a45f601 updated vision to dismiss buffered frames 2021-04-02 13:52:01 +02:00
Joel Klimont
c1451d27b5 updated vision to dismiss buffered frames 2021-04-02 13:51:31 +02:00
Joel Klimont
0e43f88fda updated vision to dismiss buffered frames 2021-04-01 00:19:59 +02:00
Konstantin Lampalzer
8bae96c8a9
Fix linefollower example 2021-03-28 21:42:44 +02:00
Konstantin Lampalzer
befc7fded6
Merge branch 'master' of github.com:F-WuTS/compLIB 2021-03-28 18:52:26 +02:00
Konstantin Lampalzer
e94d735e24
Add IRWrapper and change IRSensor logic 2021-03-28 16:39:54 +02:00
Joel
4d5b5a5063
update package
included missing dependency
2021-03-19 15:24:46 +01:00
HerrNamenlos123
3101e56252 Lowered minimal battery voltage and added ir sensor example 2021-03-19 15:16:16 +01:00
HerrNamenlos123
0ddc8e6c74 Added support for new analog IR sensors 2021-03-19 15:09:21 +01:00
Joel
ee9432317a
/getScores is now implemented in api 2021-03-05 15:45:34 +01:00
Joel
a7d1fca14a
api position objects now implements __str__ and __repr__ 2021-03-04 21:48:54 +01:00
Konstantin Lampalzer
08675bdc5c
Fix IR initial state 2021-02-12 17:43:10 +01:00
Joel
d468732040
changed api according to specification 2021-02-10 22:41:23 +01:00
Joel
5e46e8068a
changed api client according to specification
bug fixes in VisionDaemon
2021-02-10 03:22:55 +01:00
Joel
ce6b06544a
when importing complib a version check is now performed and the user is notified if he is using an outdated version 2021-02-06 23:51:24 +01:00
Joel
90e3dd6f00
added streaming commands 2021-02-06 02:43:20 +01:00
Joel
d4b577046e
vision module is not using rtmp stream anymore
jpeg webstream now runs with nearly zerolatency (currently limited to 640x480px)
2021-02-04 16:02:45 +01:00
Joel
6c579bb5fb
small changes and fixes 2021-01-30 01:53:25 +01:00
Konstantin Lampalzer
76a01e4cd9
Fix servo imports 2021-01-29 17:45:22 +01:00
Joel
af3f832758
fixed buzzer thread
fixed logging
fixed postinstall.sh
2021-01-29 14:21:03 +01:00
Joel
efd03d71a1
buzzer is now starting to issue a warning if battery is under 15% 2021-01-28 23:15:21 +01:00
koka
7ec6854ac6 Add servos 2021-01-28 22:12:33 +00:00
Joel
acc91cdd74
added option to disable logging 2021-01-28 14:21:30 +01:00
Joel
e02b42060b
fixed error in logger causing rpi to think logging server is down 2021-01-26 21:18:32 +01:00
Joel
4d699181c3
Merge remote-tracking branch 'origin/master' 2021-01-26 20:36:48 +01:00
Joel
c70edd4166
fixed vision documentation 2021-01-26 20:36:40 +01:00
Konstantin Lampalzer
4558269795
logs errors to kibana 2021-01-24 20:44:37 +01:00
Joel
dfb66d5383
added missing dependency in .deb package 2021-01-23 21:57:28 +01:00
Joel
4e1f22fa84
now overwriting nginx conf if it already exists 2021-01-23 19:05:02 +01:00
Joel
65d253a7b9
added more dependencies 2021-01-23 18:47:33 +01:00
itssme
1bd7f09e48 Update issue templates 2021-01-23 18:43:41 +01:00
Konstantin Lampalzer
4a5f630d40
Logging, more documentation 2021-01-23 13:37:40 +01:00
Konstantin Lampalzer
6eebab871e Merge branch 'master' of https://github.com/F-WuTS/compLIB 2021-01-23 11:56:55 +00:00
Konstantin Lampalzer
a544133e92 Change IR Sensor, fix motor direction 2021-01-23 11:55:56 +00:00
Joel
7e9192072c
vision daemon now streams with a resolution of 1280x720
fixed package install
2021-01-23 02:41:19 +01:00
Joel
77c2354c00
updated to version 0.0.3
added vision daemon which runs in the background
added more documentation to vision module
now including opencv dependencies in complib package
2021-01-21 23:55:26 +01:00
Joel
b06962e955
started working on vision package 2021-01-21 19:37:04 +01:00
Konstantin Lampalzer
ce1c82a192 Add more logging and exception 2021-01-21 16:43:01 +00:00
Joel
cb10bb3a4d
added dependency "python3-pip" to package 2021-01-18 20:07:43 +01:00
Konstantin Lampalzer
00d6c54cdc
Fix usage example 2021-01-18 20:02:29 +01:00
Konstantin Lampalzer
210d377e9b
Fix api documentation 2021-01-17 15:07:11 +01:00
Joel
7de2d921d8
fixed error in doc build script, causing html folder to be copied instead of its contents 2021-01-17 04:50:10 +01:00
Joel
5fbe0bcb8d
added examples to Api.rst
updated version
changes in build
2021-01-17 04:36:25 +01:00
Joel
327f87ca34
chnages in build 2021-01-17 03:04:38 +01:00
Joel
24817dcc32
updated to newest version 2021-01-16 23:56:39 +01:00
Konstantin Lampalzer
3b18b0a66c
Documentation 2021-01-16 23:41:39 +01:00
Joel
2cac75fc9c
Merge remote-tracking branch 'origin/master' 2021-01-16 23:33:19 +01:00
Joel
134cd3a30e
added more api documentation
new build process
2021-01-16 23:33:09 +01:00
HerrNamenlos123
8bc35858fb Added Ultrasonic sensor 2021-01-16 22:40:33 +01:00
joel
73284ceab8 removed .idea from git tracking
added new logging dependency
2021-01-16 21:21:00 +01:00
Konstantin Lampalzer
b2a085eab3
Add logstash logger 2021-01-16 20:44:53 +01:00
joel
1b6fd1cfdf small changes 2021-01-16 18:55:10 +01:00
Tamas Sel
b1478b77b4 Corrected a calculation error in the battery percentage 2021-01-16 15:06:53 +01:00
joel
0e0a61d710 finished api client implementation
added tests for api (compapi should run in the background or be installed)
2021-01-16 03:09:31 +01:00
Konstantin Lampalzer
c25715de40
Fix documentation 2021-01-16 02:13:00 +01:00
Konstantin Lampalzer
d1a50d4bfc
Fix documentation 2021-01-16 02:11:22 +01:00
Konstantin Lampalzer
d3a6fcd42c
Fix documentation 2021-01-16 02:11:15 +01:00
Konstantin Lampalzer
daef7c97e4
Rename folder 2021-01-16 02:10:40 +01:00
Konstantin Lampalzer
651713c081
Remove useless stuff 2021-01-16 02:07:18 +01:00
Konstantin Lampalzer
2835f3ae42
Add how to document your code in readme 2021-01-16 02:01:07 +01:00
Konstantin Lampalzer
ec0b5562cf
Add code examples 2021-01-16 01:55:22 +01:00
Konstantin Lampalzer
aa33dc36d8
Add ghpages script 2021-01-16 01:32:48 +01:00
87 changed files with 5032 additions and 573 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: HerrNamenlos123, itssme, Kola50011
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behaviour:
1. import compLib '...'
2. run this code '....'
3. See error
**Expected behaviour**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Device**
- Raspberry Pie: [e.g. 3b+]
- OS: [e.g. debian buster]
- Version [e.g. 0.0.3]
**Additional context**
Add any other context about the problem here.

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: HerrNamenlos123, itssme, Kola50011
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

60
.github/workflows/complib-package.yml vendored Normal file
View file

@ -0,0 +1,60 @@
name: Build comlib.deb package
on:
push:
branches:
- master
jobs:
build-complib-deb:
runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Bump version number
uses: paulhatch/semantic-version@v4.0.2
id: next_semantic_version
with:
branch: "master"
tag_prefix: ""
major_pattern: "(MAJOR)"
minor_pattern: "(MINOR)"
format: "${major}.${minor}.${patch}-${increment}"
bump_each_commit: true
- name: Print Version Numbers
run: |
echo ${{join(steps.next_semantic_version.outputs.*, ' - ')}}
echo "Next version: ${{steps.next_semantic_version.outputs.version}}"
echo "Next version tag: ${{steps.next_semantic_version.outputs.version_tag}}"
- name: Tag Commit
uses: actions/github-script@v5
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/${{steps.next_semantic_version.outputs.version}}',
sha: context.sha
})
- name: Install fpm
run: sudo apt install ruby -y && sudo gem install fpm
- name: Build complib deb Package
env:
VERSION: ${{steps.next_semantic_version.outputs.version}}
run: bash ./build.sh # creates packages in "output" directory
- name: Pushes to another repository
uses: cpina/github-action-push-to-another-repository@main
env:
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
with:
source-directory: 'output'
target-directory: 'debs/complib/'
destination-github-username: 'F-WuTS'
destination-repository-name: 'compREP'
user-email: joel.klimont@comp-air.at
target-branch: master

90
.gitignore vendored
View file

@ -1,12 +1,38 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,python,pycharm
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,python,pycharm
# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm,code
# Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm,code
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
### Code ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
*.code-workspace
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
@ -19,6 +45,9 @@
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
@ -69,6 +98,9 @@ atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
@ -111,6 +143,10 @@ fabric.properties
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
@ -134,7 +170,6 @@ parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
@ -164,7 +199,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log
cover/
# Translations
*.mo
@ -185,9 +220,9 @@ instance/
# Sphinx documentation
docs/_build/
doc/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
@ -198,7 +233,9 @@ profile_default/
ipython_config.py
# pyenv
.python-version
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
@ -207,7 +244,22 @@ ipython_config.py
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
@ -225,7 +277,6 @@ venv/
ENV/
env.bak/
venv.bak/
pythonenv*
# Spyder project settings
.spyderproject
@ -248,7 +299,14 @@ dmypy.json
# pytype static type analyzer
.pytype/
# profiling data
.prof
# Cython debug symbols
cython_debug/
# End of https://www.toptal.com/developers/gitignore/api/python,pycharm,code
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# End of https://www.toptal.com/developers/gitignore/api/macos,python,pycharm

View file

@ -1,8 +1,50 @@
# compLIB
## TODOs
Rewrite for ROS is the live packaged version since 18.07.2023.
### LED control is not implemented
# Dependencies
Need dependencies:
https://github.com/Freenove/Freenove_RPI_WS281x_Python
TODO: document
## Building documentation
```
pip install sphinx-rtd-theme
```
* [Sphinx](https://www.sphinx-doc.org/en/master/usage/installation.html)
# Writing docs
[Inline documentation example](https://pythonhosted.org/an_example_pypi_project/sphinx.html#full-code-example)
[reStructured Text](https://pythonhosted.org/an_example_pypi_project/sphinx.html#restructured-text-rest-resources)
# ENV Variables
+ `DEBUG`, default="0", If set to != "0" (default), debug prints will be enabled
+ `API_URL`, default="http://localhost:5000/"
+ `API_FORCE`, default="", if set to !="" (default), it will replace the API_URL env variable
+ `FORCE_SEED`, default="-1", if set to !="-1" (default), the seeding seed supplied by the user will be ignored and this seed will be used instead
# Stream Video
```
sudo raspivid -t 0 -b 10000000 -w 1920 -h 1080 -fps 30 -n -o - | gst-launch-1.0 fdsrc ! video/x-h264,width=1280,height=720,framerate=30/1,noise-reduction=1,profile=high,stream-format=byte-stream ! h264parse ! queue ! flvmux streamable=true ! rtmpsink location=\"rtmp://10.1.1.68/live/stream\"```
```
## Stream video from generic USB camera
with gst, very cpu heavy
```
gst-launch-1.0 v4l2src ! image/jpeg,width=640,height=480,framerate=30/1 ! jpegdec ! x264enc ! flvmux streamable=true ! rtmpsink location="rtmp://10.20.86.88/live/stream"
```
with ffmpeg, cpu friendly
```
ffmpeg -f v4l2 -framerate 30 -video_size 640x480 -i /dev/video0 -b:v 2M -f flv rtmp://10.20.86.88/live/stream
```
# Bullseye now only supports libcamera
https://www.raspberrypi.com/news/bullseye-camera-system/
(This can still be mitigated by enabling "old camera support" in the raspi-config settings. (This is done automatically in the postinstallscript)

View file

@ -1,7 +1,9 @@
#!/usr/bin/env bash
python3 setup.py sdist
cd dist || exit
tar -xzmf *.tar.gz
cd complib-0.0.1 || exit
debmake -b":python3"
gbp buildpackage --git-sign-tags --git-keyid=97B61E4D515353C0A498D2AB22680B5AAC0C4FCE --git-ignore-new # git ignore while developing
#!/usr/bin/bash
mkdir output
DEB="empty"
source build_deb.sh
echo "Ran build deb, created: $DEB"
mv $DEB ./output

61
build_deb.sh Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/bash
export PYTHONDONTWRITEBYTECODE=1
# set to 1 for debugging
export EXTRACT_PKG="0"
if [[ -z $VERSION ]]; then
echo "Warning, setting VERSION env var to default value 0.0.1-0"
export VERSION="0.0.1-0"
fi
echo "Building Package version: $VERSION"
# BE CAREFUL TO NOT BUILD IN A PYTHON VENV!
# be sure to change version if needed!
fpm -s python --python-bin python3 --python-package-name-prefix python3 \
-m '"Joel Klimont" <joel.klimont@comp-air.at>' \
--license 'proprietary' \
--description 'Library for robot used in the competition' \
--after-install postinstall.sh \
--after-upgrade postinstall.sh \
--deb-priority "optional" \
--architecture "aarch64" \
-d "python3-pip" \
-d "nginx" \
-d "libnginx-mod-rtmp" \
-d "libatlas-base-dev" \
-d "python3-numpy" \
-d "opencv-dev" \
-d "opencv-libs" \
-d "opencv-licenses" \
-d "opencv-main" \
-d "opencv-python" \
-d "opencv-scripts" \
-d "libprotobuf23" \
-d "protobuf-compiler" \
-d "python3-protobuf" \
--python-install-lib "/usr/local/lib/python3.9/dist-packages" \
-v $VERSION -t deb setup.py
if [ "$EXTRACT_PKG" == "1" ]; then
echo "Extracting deb package"
mkdir build_extract
mv python3-complib_"$VERSION"_arm64.deb build_extract
cd build_extract
ar -xv python3-complib_"$VERSION"_arm64.deb
fi
export DEB=python3-complib_"$VERSION"_arm64.deb
echo "Created: $DEB"
# --deb-changelog changelog \
# --deb-upstream-changelog changelog \
# --deb-field "Distribution: stable" \
# --deb-dist "stable" \
# sudo apt purge python3-complib -y
# sudo apt install ./python3-*
# sudo apt search complib
# ar vx ./python3*

View file

@ -1,35 +0,0 @@
import smbus
SINGLE_ENDED = 0x84
ADDRESS = 0x48
bus = smbus.SMBus(1)
class ADC:
@staticmethod
def read(channel) -> float:
"""
Read from adc channel
0 -> Left IR Sensor
1 -> Right IR Sensor
2 -> Battery voltage / 3
:param channel: Channel between 0 and 2
:return: voltage
"""
"""Select the Command data from the given provided value above"""
command = SINGLE_ENDED | ((((channel << 2) | (channel >> 1)) & 0x07) << 4)
bus.write_byte(ADDRESS, command)
while "Koka is great":
value1 = bus.read_byte(ADDRESS)
value2 = bus.read_byte(ADDRESS)
if value1 == value2:
break
voltage = value1 / 255.0 * 3.3 # calculate the voltage value
voltage = round(voltage, 2)
return voltage

View file

@ -1,51 +0,0 @@
import requests
import json
import os
API_URL = os.getenv("API_URL", "http://localhost:5000/")
API_URL_GET_POS = API_URL + "getPos"
API_URL_GET_OP = API_URL + "getOp"
API_URL_GET_GOAL = API_URL + "getGoal"
API_URL_GET_ITEMS = API_URL + "getItems"
class Seeding:
@staticmethod
def get_park():
pass
@staticmethod
def pay_park():
pass
@staticmethod
def simon_says():
pass
class Position:
def __init__(self, x, y, rotation):
self.x = x
self.y = y
self.rotation = rotation
class DoubleElim:
@staticmethod
def get_position():
response = json.loads(requests.get(API_URL_GET_POS).content)
return Position(response["x"], response["y"], response["degrees"])
@staticmethod
def get_opponent():
response = json.loads(requests.get(API_URL_GET_OP).content)
return Position(response["x"], response["y"], response["degrees"])
@staticmethod
def get_goal():
response = json.loads(requests.get(API_URL_GET_GOAL).content)
return Position(response["x"], response["y"], -1)
@staticmethod
def get_items():
return json.loads(requests.get(API_URL_GET_ITEMS).content)

View file

@ -1,24 +0,0 @@
from compLIB.ADC import ADC
BATTERY_CHANNEL = 2
BATTERY_COUNT = 2
BATTERY_MULTIPLIER = 3
BATTERY_MIN_VOLTAGE = 3.6
BATTERY_MAX_VOLTAGE = 4.1
adc = ADC()
class Battery(object):
"""Used to interact with the battery
"""
@staticmethod
def percent() -> int:
"""Get battery percentage
:return: Percentage between 0 and 100
:rtype: int
"""
voltage = adc.read(BATTERY_CHANNEL) * BATTERY_MULTIPLIER
return int((voltage - BATTERY_MIN_VOLTAGE) / BATTERY_MAX_VOLTAGE * 100)

View file

@ -1,28 +0,0 @@
import atexit
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
Buzzer_Pin = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(Buzzer_Pin, GPIO.OUT)
class Buzzer:
"""Used to interact with the buzzer
"""
@staticmethod
def set(on: bool):
"""Turn the buzzer on / off
:param on: True if on, False if off
"""
GPIO.output(Buzzer_Pin, on)
@staticmethod
def exit():
Buzzer.set(0)
atexit.register(Buzzer.exit)

View file

@ -1,73 +0,0 @@
import RPi.GPIO as GPIO
from compLIB.ADC import ADC
TOP_LEFT_CHANNEL = 0
TOP_RIGHT_CHANNEL = 1
TOP_IR_MIN_VOLTAGE = 0.0
TOP_IR_MAX_VOLTAGE = 3.3
BOTTOM_LEFT_PIN = 14
BOTTOM_MIDDLE_PIN = 15
BOTTOM_RIGHT_PIN = 23
adc = ADC()
GPIO.setmode(GPIO.BCM)
GPIO.setup(BOTTOM_LEFT_PIN, GPIO.IN)
GPIO.setup(BOTTOM_MIDDLE_PIN, GPIO.IN)
GPIO.setup(BOTTOM_RIGHT_PIN, GPIO.IN)
class IRSensor(object):
"""Access the different IR Sensors at top / bottom of the robot
"""
@staticmethod
def top_left_percent() -> int:
"""Get top left infrared sensor percentage
:return: Percentage between 0 and 100
:rtype: int
"""
voltage = adc.read(TOP_LEFT_CHANNEL)
return int((voltage - TOP_IR_MIN_VOLTAGE) / TOP_IR_MAX_VOLTAGE * 100)
@staticmethod
def top_right_percent() -> int:
"""Get top right infrared sensor percentage
:return: Percentage between 0 and 100
:rtype: int
"""
voltage = adc.read(TOP_RIGHT_CHANNEL)
return int((voltage - TOP_IR_MIN_VOLTAGE) / TOP_IR_MAX_VOLTAGE * 100)
@staticmethod
def bottom_left() -> bool:
"""Get bottom left infrared sensor status
:return: True, if sensor detects IR signals
:rtype: bool
"""
return GPIO.input(BOTTOM_LEFT_PIN)
@staticmethod
def bottom_middle() -> bool:
"""Get bottom middle infrared sensor status
:return: True, if sensor detects IR signals
:rtype: bool
"""
return GPIO.input(BOTTOM_MIDDLE_PIN)
@staticmethod
def bottom_right() -> bool:
"""Get bottom right infrared sensor status
:return: True, if sensor detects IR signals
:rtype: bool
"""
return GPIO.input(BOTTOM_RIGHT_PIN)

View file

@ -1,51 +0,0 @@
import atexit
from compLIB.PCA9685 import PCA9685
pwm = PCA9685(0x40, debug=True)
pwm.setPWMFreq(50)
MOTOR_COUNT = 4
MAX_MOTOR_SPEED = 4095.0
MOTOR_PERCENTAGE_MULT = MAX_MOTOR_SPEED / 100.0
class Motor(object):
"""Class used to control the motors
"""
@staticmethod
def power(port: int, percent: int):
"""Set specified motor to percentage power
:param port: Port, which the motor is connected to. 0-3, 0 -> top left, 3 -> top right
:param percent: Percentage of max speed. between -100 and 100
"""
forward = True
if percent < 0:
percent = abs(percent)
forward = False
# bottom left motor is inverted - REEEEEEEEEEEE
if port == 1:
forward = not forward
adjusted_speed = int(min(max(0, percent), 100) * MOTOR_PERCENTAGE_MULT)
if forward:
pwm.setMotorPwm(port * 2, 0)
pwm.setMotorPwm(port * 2 + 1, adjusted_speed)
else:
pwm.setMotorPwm(port * 2, adjusted_speed)
pwm.setMotorPwm(port * 2 + 1, 0)
@staticmethod
def all_off():
"""
Turns of all motors
"""
for i in range(0, MOTOR_COUNT):
Motor.power(i, 0)
atexit.register(Motor.all_off)

View file

@ -1,77 +0,0 @@
#!/usr/bin/python
import math
import time
import smbus
# ============================================================================
# Raspi PCA9685 16-Channel PWM Servo Driver
# ============================================================================
class PCA9685:
# Registers/etc.
__SUBADR1 = 0x02
__SUBADR2 = 0x03
__SUBADR3 = 0x04
__MODE1 = 0x00
__PRESCALE = 0xFE
__LED0_ON_L = 0x06
__LED0_ON_H = 0x07
__LED0_OFF_L = 0x08
__LED0_OFF_H = 0x09
__ALLLED_ON_L = 0xFA
__ALLLED_ON_H = 0xFB
__ALLLED_OFF_L = 0xFC
__ALLLED_OFF_H = 0xFD
def __init__(self, address=0x40, debug=False):
self.bus = smbus.SMBus(1)
self.address = address
self.debug = debug
self.write(self.__MODE1, 0x00)
def write(self, reg, value):
"Writes an 8-bit value to the specified register/address"
self.bus.write_byte_data(self.address, reg, value)
def read(self, reg):
"Read an unsigned byte from the I2C device"
result = self.bus.read_byte_data(self.address, reg)
return result
def setPWMFreq(self, freq):
"Sets the PWM frequency"
prescaleval = 25000000.0 # 25MHz
prescaleval /= 4096.0 # 12-bit
prescaleval /= float(freq)
prescaleval -= 1.0
prescale = math.floor(prescaleval + 0.5)
oldmode = self.read(self.__MODE1)
newmode = (oldmode & 0x7F) | 0x10 # sleep
self.write(self.__MODE1, newmode) # go to sleep
self.write(self.__PRESCALE, int(math.floor(prescale)))
self.write(self.__MODE1, oldmode)
time.sleep(0.005)
self.write(self.__MODE1, oldmode | 0x80)
def setPWM(self, channel, on, off):
"Sets a single PWM channel"
self.write(self.__LED0_ON_L + 4 * channel, on & 0xFF)
self.write(self.__LED0_ON_H + 4 * channel, on >> 8)
self.write(self.__LED0_OFF_L + 4 * channel, off & 0xFF)
self.write(self.__LED0_OFF_H + 4 * channel, off >> 8)
def setMotorPwm(self, channel, duty):
self.setPWM(channel, 0, duty)
def setServoPulse(self, channel, pulse):
"Sets the Servo Pulse,The PWM frequency must be 50HZ"
pulse = pulse * 4096 / 20000 # PWM frequency is 50HZ,the period is 20000us
self.setPWM(channel, 0, int(pulse))
if __name__ == '__main__':
pass

View file

@ -1,30 +0,0 @@
from compLIB.PCA9685 import PCA9685
pwm = PCA9685(0x40, debug=True)
pwm.setPWMFreq(50)
class Servo:
"""Control the servo ports on the robot
"""
@staticmethod
def set_position(channel: int, angle: int):
"""Set position of servo connected to port
:param channel: channel between 0 and 7
:param angle: Angle of servo
"""
angle = abs(angle)
if channel == 0:
pwm.setServoPulse(8 + channel, 2500 - int(angle / 0.09))
elif channel < 8:
pwm.setServoPulse(8 + channel, 500 - int(angle / 0.09))
@staticmethod
def setup_position():
"""Set position of servos to the position used during the setup process
"""
pwm.setServoPulse(8, 1500)
pwm.setServoPulse(9, 1500)

View file

@ -1,106 +0,0 @@
import time
from Motor import *
import RPi.GPIO as GPIO
from servo import *
from PCA9685 import PCA9685
class Ultrasonic:
def __init__(self):
GPIO.setwarnings(False)
self.trigger_pin = 27
self.echo_pin = 22
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.trigger_pin, GPIO.OUT)
GPIO.setup(self.echo_pin, GPIO.IN)
def send_trigger_pulse(self):
GPIO.output(self.trigger_pin, True)
time.sleep(0.00015)
GPIO.output(self.trigger_pin, False)
def wait_for_echo(self, value, timeout):
count = timeout
while GPIO.input(self.echo_pin) != value and count > 0:
count = count - 1
def get_distance(self):
distance_cm = [0, 0, 0, 0, 0]
for i in range(3):
self.send_trigger_pulse()
self.wait_for_echo(True, 10000)
start = time.time()
self.wait_for_echo(False, 10000)
finish = time.time()
pulse_len = finish - start
distance_cm[i] = pulse_len / 0.000058
distance_cm = sorted(distance_cm)
return int(distance_cm[2])
def run_motor(self, L, M, R):
if (L < 30 and M < 30 and R < 30) or M < 30:
self.PWM.setMotorModel(-1450, -1450, -1450, -1450)
time.sleep(0.1)
if L < R:
self.PWM.setMotorModel(1450, 1450, -1450, -1450)
else:
self.PWM.setMotorModel(-1450, -1450, 1450, 1450)
elif L < 30 and M < 30:
PWM.setMotorModel(1500, 1500, -1500, -1500)
elif R < 30 and M < 30:
PWM.setMotorModel(-1500, -1500, 1500, 1500)
elif L < 20:
PWM.setMotorModel(2000, 2000, -500, -500)
if L < 10:
PWM.setMotorModel(1500, 1500, -1000, -1000)
elif R < 20:
PWM.setMotorModel(-500, -500, 2000, 2000)
if R < 10:
PWM.setMotorModel(-1500, -1500, 1500, 1500)
else:
self.PWM.setMotorModel(600, 600, 600, 600)
def run(self):
self.PWM = Motor()
self.pwm_S = Servo()
for i in range(30, 151, 60):
self.pwm_S.setServoPwm('0', i)
time.sleep(0.2)
if i == 30:
L = self.get_distance()
elif i == 90:
M = self.get_distance()
else:
R = self.get_distance()
while True:
for i in range(90, 30, -60):
self.pwm_S.setServoPwm('0', i)
time.sleep(0.2)
if i == 30:
L = self.get_distance()
elif i == 90:
M = self.get_distance()
else:
R = self.get_distance()
self.run_motor(L, M, R)
for i in range(30, 151, 60):
self.pwm_S.setServoPwm('0', i)
time.sleep(0.2)
if i == 30:
L = self.get_distance()
elif i == 90:
M = self.get_distance()
else:
R = self.get_distance()
self.run_motor(L, M, R)
ultrasonic = Ultrasonic()
# Main program logic follows:
if __name__ == '__main__':
print('Program is starting ... ')
try:
ultrasonic.run()
except KeyboardInterrupt: # When 'Ctrl+C' is pressed, the child program destroy() will be executed.
PWM.setMotorModel(0, 0, 0, 0)
ultrasonic.pwm_S.setServoPwm('0', 90)

View file

@ -1 +0,0 @@
__version__ = "0.0.1"

312
compLib/.gitignore vendored Normal file
View file

@ -0,0 +1,312 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,python,pycharm
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,python,pycharm
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PyCharm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# End of https://www.toptal.com/developers/gitignore/api/macos,python,pycharm

70
compLib/Api.py Normal file
View file

@ -0,0 +1,70 @@
import json
import logging
import os
from typing import Dict, Tuple, List
import requests
logger = logging.getLogger("complib-logger")
API_URL = os.getenv("API_URL", "http://localhost:5000/") + "api/"
CONF_URL = os.getenv("API_URL", "http://localhost:5000/") + "config/"
api_override = os.getenv("API_FORCE", "")
if api_override != "":
print(f"API_URL was set to {API_URL} but was overwritten with {api_override}")
API_URL = api_override
API_URL_GET_HEU = API_URL + "getHeuballen"
API_URL_GET_LOGISTIC_PLAN = API_URL + "getLogisticPlan"
API_URL_GET_MATERIAL_DELIVERIES = API_URL + "getMaterialDeliveries"
API_URL_GET_ROBOT_STATE = API_URL + "getRobotState"
class Seeding:
"""Klasse welche mit der Seeding API Kommuniziert.
"""
@staticmethod
def get_heuballen() -> int:
"""Macht den /api/getHeuballen request zur Seeding API.
:return: hueballencode als int.
:rtype: int
"""
res = requests.get(API_URL_GET_HEU)
result = json.loads(res.content)
logger.debug(f"Seeding.get_heuballen = {result}, status code = {res.status_code}")
return result["heuballen"]
@staticmethod
def get_logistic_plan() -> List:
"""Macht den /api/getLogisticPlan zur Seeding API.
:return: Liste an logistic-centern, welche vom roboter in genau der Reihenfolge beliefert werden sollten.
:rtype: List
"""
res = requests.get(API_URL_GET_LOGISTIC_PLAN)
result = json.loads(res.content)
logger.debug(f"Seeding.get_logistic_plan = {result}, status code = {res.status_code}")
return result
@staticmethod
def get_material_deliveries() -> List:
"""Macht den /api/getMaterialDeliveries zur Seeding API.
:return: Json Object and status code as returned by the api.
:rtype: List
"""
res = requests.get(API_URL_GET_MATERIAL_DELIVERIES)
result = json.loads(res.content)
logger.debug(f"Seeding.get_material_deliveries = {result}, status code = {res.status_code}")
return result
@staticmethod
def get_robot_state() -> Tuple[Dict, int]:
res = requests.get(API_URL_GET_ROBOT_STATE)
result = json.loads(res.content)
logger.debug(f"Seeding.get_robot_state {result}, status code = {res.status_code}")
return result, res.status_code

55
compLib/CMakeLists.txt Normal file
View file

@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.26)
project(comp_lib)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(std_msgs REQUIRED)
find_package(irobot_create_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# add_executable(talker src/publisher_member_function.cpp)
# ament_target_dependencies(talker rclcpp std_msgs irobot_create_msgs)
# add_executable(listener src/subscriber_member_function.cpp)
# ament_target_dependencies(listener rclcpp std_msgs irobot_create_msgs)
# add_executable(drive src/drive_action.cpp)
# ament_target_dependencies(drive rclcpp rclcpp_action std_msgs irobot_create_msgs)
# add_executable(set_vel src/set_speed.cpp)
# ament_target_dependencies(set_vel rclcpp rclcpp_action std_msgs irobot_create_msgs geometry_msgs)
add_executable(compLib_node src/main.cpp src/motor.cpp src/controls.cpp)
ament_target_dependencies(compLib_node rclcpp rclcpp_action std_msgs irobot_create_msgs geometry_msgs)
target_include_directories(compLib_node PRIVATE include)
install(TARGETS
#talker
#listener
#drive
#set_vel
compLib_node
DESTINATION lib/${PROJECT_NAME})
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()

231
compLib/Camera.py Normal file
View file

@ -0,0 +1,231 @@
import sys
from typing import Any, Tuple, List
# build image is somehow different from raspberry image? opencv-python is installed to a directory which is not in the pythonpath by default....
sys.path.append("/usr/lib/python3.9/site-packages")
import logging
import os
import queue
import threading
import cv2
from flask import Flask, Response
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
SERVE_VIDEO = os.getenv("SERVER_SRC", "/live")
BUILDING_DOCS = os.getenv("BUILDING_DOCS", "false")
HTML = """
<html>
<head>
<title>Opencv Output</title>
</head>
<body>
<h1>Opencv Output</h1>
<img src="{{ VIDEO_DST }}">
</body>
</html>
"""
# it would be better to use jinja2 here, but I don't want to blow up the package dependencies...
HTML = HTML.replace("{{ VIDEO_DST }}", SERVE_VIDEO)
class Marker:
def __init__(self, id: int, x: float, y: float):
self.id: int = id
self.x: float = x
self.y: float = y
def __str__(self) -> str:
return f"Marker ID: {self.id}, position: {self.x} x, {self.y} y"
def __repr__(self) -> str:
return str({"id": self.id, "x": self.x, "y": self.y})
class Camera:
class __Webserver:
def __init__(self, camera):
self.app = Flask(__name__)
self.__camera = camera
self.__thread = threading.Thread(target=self.__start_flask, daemon=True)
self.__thread.start()
@self.app.route("/live")
def __video_feed():
"""
Define route for serving jpeg stream.
:return: Return the response generated along with the specific media.
"""
return Response(self.__camera._newest_frame_generator(),
mimetype="multipart/x-mixed-replace; boundary=frame")
@self.app.route("/")
def __index():
"""
Define route for serving a static http site to view the stream.
:return: Static html page where the video stream of Opencv can be viewed.
"""
return HTML
def __start_flask(self):
"""
Function for running flask server in a thread.
:return:
"""
logging.getLogger("complib-logger").info("starting flask server")
self.app.run(host="0.0.0.0", port=9898, debug=True, threaded=True, use_reloader=False)
class __NoBufferVideoCapture:
def __init__(self, cam):
self.cap = cv2.VideoCapture(cam)
self.cap.set(3, 640)
self.cap.set(4, 480)
self.q = queue.Queue(maxsize=3)
self.stopped = False
self.t = threading.Thread(target=self._reader, daemon=True)
self.t.start()
def _reader(self):
while not self.stopped:
ret, frame = self.cap.read()
if not ret:
continue
if self.q.full():
try:
self.q.get_nowait()
except queue.Empty:
pass
self.q.put(frame)
def read(self):
return self.q.get()
def stop(self):
self.stopped = True
self.t.join()
def __init__(self):
self.__logger = logging.getLogger("complib-logger")
self.__logger.info("capturing rtmp stream is disabled in this version")
self.__camera_stream = self.__NoBufferVideoCapture(-1)
self.__newest_frame = None
self.__lock = threading.Lock()
self.__webserver = self.__Webserver(self)
self.aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_50)
self.aruco_params = cv2.aruco.DetectorParameters_create()
self.__logger.info("Initialized vision")
def get_frame(self) -> Any:
"""
Die Funktion gibt das neuste Bild, welches die Kamera aufgenommen, hat zurück.
:return: Ein "opencv image frame"
"""
img16 = self.__camera_stream.read()
return img16
def detect_markers(self, image) -> Any:
"""
Funktion um die ArUco Marker in einem Bild zu erkennen.
:param image: Bild, welches die Kamera aufgenommen hat.
:return: Gibt drei Variablen zurueck. Erstens eine Liste an Postionen der "Ecken" der erkannten Markern. Zweitens eine Liste an IDs der erkannten Markern und dritten noch Debug Informationen (diese können ignoriert werden).
"""
return cv2.aruco.detectMarkers(image, self.aruco_dict, parameters=self.aruco_params)
def detect_markers_midpoint(self, image) -> Tuple[List[Marker], Any]:
"""
Funktion um die ArUco Marker in einem Bild zu erkennen, einzuzeichnen und den Mittelpunkt der Marker auszurechnen.
:param image: Bild, welches die Kamera aufgenommen hat.
:return: Gibt zwei Variablen zurueck. Erstens eine Liste an "Markern" und zweitens das Bild mit den eigezeichneten Marken.
:rtype: Tuple[List[Marker], Any]
"""
(corners, ids, rejected) = self.detect_markers(image)
self.draw_markers(image, corners, ids)
res = []
for i in range(0, len(corners)):
x = sum([point[0] for point in corners[i][0]]) / 4
y = sum([point[1] for point in corners[i][0]]) / 4
res.append(Marker(ids[i][0], x, y))
return res, image
def draw_markers(self, image, corners, ids) -> Any:
"""
Zeichnet die erkannten Markern mit ihren IDs in das Bild.
:param image: Original Bild, in dem die Marker erkannt wurden.
:param corners: List der Positionen der Ecken der erkannten Marker.
:param ids: IDs der erkannten Markern.
:return: Neues Bild mit den eigezeichneten Markern.
"""
return cv2.aruco.drawDetectedMarkers(image, corners, ids)
def publish_frame(self, image):
"""
Sendet das Bild, welches der Funktion übergeben wird, an den Webserver, damit es der Nutzer in seinem Browser ansehen kann.
:param image: Opencv Bild, welches dem Nutzer angezeigt werden soll.
:return: None
"""
with self.__lock:
if image is not None:
self.__newest_frame = image.copy()
def _newest_frame_generator(self):
"""
Private generator which is called directly from flask server.
:return: Yields image/jpeg encoded frames published from publish_frame function.
"""
while True:
# use a buffer frame to copy the newest frame with lock and then freeing it immediately
buffer_frame = None
with self.__lock:
if self.__newest_frame is None:
continue
buffer_frame = self.__newest_frame.copy()
# encode frame for jpeg stream
(flag, encoded_image) = cv2.imencode(".jpg", buffer_frame)
# if there was an error try again with the next frame
if not flag:
continue
# else yield encoded frame with mimetype image/jpeg
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
bytearray(encoded_image) + b'\r\n')
# for debugging and testing start processing frames and detecting a 6 by 9 calibration chessboard
if __name__ == '__main__' and BUILDING_DOCS == "false":
camera = Camera()
while True:
image = camera.get_frame()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# processing
# gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# find the chessboard corners
# ret, corners = cv2.findChessboardCorners(gray, (6, 9), None)
# cv2.drawChessboardCorners(frame, (6, 9), corners, ret)
markers, image = camera.detect_markers_midpoint(image)
print(markers)
print("-----------------")
camera.publish_frame(image)

126
compLib/CompLib.proto Normal file
View file

@ -0,0 +1,126 @@
syntax = "proto3";
package CompLib;
message Header {
string message_type = 1;
}
message Status {
bool successful = 1;
string error_message = 2;
}
message GenericRequest {
Header header = 1;
}
message GenericResponse {
Header header = 1;
Status status = 2;
}
message EncoderReadPositionsRequest {
Header header = 1;
}
message EncoderReadPositionsResponse {
Header header = 1;
Status status = 2;
repeated int32 positions = 3 [packed = true];
}
message EncoderReadVelocitiesRequest {
Header header = 1;
}
message EncoderReadVelocitiesResponse {
Header header = 1;
Status status = 2;
repeated double velocities = 3 [packed = true];
}
message IRSensorsEnableRequest {
Header header = 1;
}
message IRSensorsDisableRequest {
Header header = 1;
}
message IRSensorsReadAllRequest {
Header header = 1;
}
message IRSensorsReadAllResponse {
Header header = 1;
Status status = 2;
repeated uint32 data = 3 [packed = true];
}
message MotorSetPowerRequest {
Header header = 1;
uint32 port = 2;
double power = 3;
}
message MotorsSetPowerRequest {
Header header = 1;
repeated MotorSetPowerRequest requests = 2;
}
message MotorSetSpeedRequest {
Header header = 1;
uint32 port = 2;
double speed = 3;
}
message MotorsSetSpeedRequest {
Header header = 1;
repeated MotorSetSpeedRequest requests = 2;
}
message MotorSetPulseWidthRequest {
Header header = 1;
uint32 port = 2;
double percent = 3;
}
message MotorsSetPulseWidthRequest {
Header header = 1;
repeated MotorSetPulseWidthRequest requests = 2;
}
message OdometryReadRequest {
Header header = 1;
}
message OdometryReadResponse {
Header header = 1;
Status status = 2;
double x_position = 3;
double y_position = 4;
double orientation = 5;
}
message DriveDistanceRequest {
Header header = 1;
double distance_m = 2;
double velocity_m_s = 3;
}
message TurnDegreesRequest {
Header header = 1;
double angle_degrees = 2;
double velocity_rad_s = 3;
}
message DriveRequest {
Header header = 1;
double linear_velocity_m_s = 2;
double angular_velocity_rad_s = 3;
}
message HealthUpdateRequest {
Header header = 1;
}

70
compLib/CompLibClient.py Normal file
View file

@ -0,0 +1,70 @@
import socket
from threading import Lock
import compLib.CompLib_pb2 as CompLib_pb2
class CompLibClient(object):
UNIX_SOCKET_PATH = "/tmp/compLib"
TCP_SOCKET_HOST = ""
TCP_SOCKET_PORT = 9090
SOCKET = None
LOCK = Lock()
@staticmethod
def use_unix_socket(socket_path="/tmp/compLib"):
CompLibClient.UNIX_SOCKET_PATH = socket_path
CompLibClient.SOCKET = socket.socket(
socket.AF_UNIX, socket.SOCK_STREAM)
CompLibClient.SOCKET.connect(CompLibClient.UNIX_SOCKET_PATH)
from compLib.HealthCheck import HealthUpdater
HealthUpdater.start()
@staticmethod
def use_tcp_socket(socket_host, socket_port=TCP_SOCKET_PORT):
CompLibClient.TCP_SOCKET_HOST = socket_host
CompLibClient.TCP_SOCKET_PORT = socket_port
CompLibClient.SOCKET = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
CompLibClient.SOCKET.connect(
(CompLibClient.TCP_SOCKET_HOST, CompLibClient.TCP_SOCKET_PORT))
from compLib.HealthCheck import HealthUpdater
HealthUpdater.start()
@staticmethod
def send(data: bytes, size: int) -> bytes:
with CompLibClient.LOCK:
if CompLibClient.SOCKET is None:
CompLibClient.use_unix_socket()
CompLibClient.SOCKET.sendall(size.to_bytes(1, byteorder='big'))
CompLibClient.SOCKET.sendall(data)
response_size_bytes = CompLibClient.SOCKET.recv(1)
response_size = int.from_bytes(
response_size_bytes, byteorder="big")
# print(response_size)
response_bytes = CompLibClient.SOCKET.recv(response_size)
# print(response_bytes.hex())
# print(len(response_bytes))
CompLibClient.check_response(response_bytes)
return response_bytes
@staticmethod
def check_response(response_bytes: bytes) -> bool:
# print(f"{response_bytes}")
res = CompLib_pb2.GenericResponse()
res.ParseFromString(response_bytes)
if res.status.successful:
return True
# TODO: Log error message if unsuccessful
return False

1212
compLib/CompLib_pb2.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,161 @@
import json
import os
import time
from typing import Tuple, List, Dict
import requests
import logging
logger = logging.getLogger("complib-logger")
RETRY_TIMEOUT = 0.05
# TODO: rethink how the api url is read
API_URL = os.getenv("API_URL", "http://localhost:5000/") + "api/"
api_override = os.getenv("API_FORCE", "")
if api_override != "":
logger.warning(f"API_URL was set to {API_URL} but was overwritten with {api_override}")
API_URL = api_override
API_URL_GET_ROBOT_STATE = API_URL + "getRobotState"
API_URL_GET_POS = API_URL + "getPos"
API_URL_GET_OP = API_URL + "getOp"
API_URL_GET_GOAL = API_URL + "getGoal"
API_URL_GET_ITEMS = API_URL + "getItems"
API_URL_GET_SCORES = API_URL + "getScores"
class Position:
"""
Datenstruktur, welche eine Position representiert.
:ivar x: X Position in Centimeter
:ivar y: Y Position in Centimeter
:ivar degrees: Rotation in Grad von -180 bis 180
"""
def __init__(self, x, y, degrees):
self.x = x
self.y = y
self.degrees = degrees
def __repr__(self):
return "{x=%s, y=%s, degrees=%s}" % (self.x, self.y, self.degrees)
def __str__(self):
return f"Position(x={round(self.x, 5)}, y={round(self.y, 5)}, degrees={round(self.degrees, 5)})"
def __eq__(self, o: object) -> bool:
if isinstance(o, Position):
return self.x == o.x and self.y == o.y and self.degrees == o.degrees
return False
def __ne__(self, o: object) -> bool:
return not self.__eq__(o)
@staticmethod
def position_from_json(json_str: Dict):
return Position(json_str["x"], json_str["y"], json_str["degrees"])
class DoubleElim:
"""Klasse für die Kommunikation mit Double Elimination Api
"""
@staticmethod
def get_pos() -> Tuple[Position, int]:
"""Führt den /api/getPos Aufruf an die API aus.
:return: Ein Objekt der Klasse :class:`.Position` mit der Position des Roboters und der Status Code
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_POS)
if res.status_code == 408:
logger.error(f"DoubleElim.get_position timeout. API={API_URL_GET_POS}")
time.sleep(RETRY_TIMEOUT)
return DoubleElim.get_pos()
elif res.status_code == 503:
return Position(0, 0, -1), 503
response = json.loads(res.content)
logger.debug(f"DoubleElim.get_position = {response}, status code = {res.status_code}")
return Position(response["x"], response["y"], response["degrees"]), res.status_code
@staticmethod
def get_opponent() -> Tuple[Position, int]:
"""Führt den /api/getOp Aufruf an die API aus.
:return: Ein Objekt der Klasse :class:`.Position` mit der Position des gegnerischen Roboters relativ zum eigenen Roboter und der Status Code
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_OP)
if res.status_code == 408:
logger.error(f"DoubleElim.get_opponent timeout. API={API_URL_GET_OP}")
time.sleep(RETRY_TIMEOUT)
return DoubleElim.get_opponent()
elif res.status_code == 503:
return Position(0, 0, -1), 503
response = json.loads(res.content)
logger.debug(f"DoubleElim.get_opponent = x:{response}, status code = {res.status_code}")
return Position(response["x"], response["y"], response["degrees"]), res.status_code
@staticmethod
def get_goal() -> Tuple[Position, int]:
"""Führt den /api/getGoal Aufruf an die API aus.
:return: Ein Objekt der Klasse :class:`.Position` mit der Position des Ziels relativ zum eigenen Roboter und der Status Code
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_GOAL)
if res.status_code == 408:
logger.error(f"DoubleElim.get_goal timeout. API={API_URL_GET_GOAL}")
time.sleep(RETRY_TIMEOUT)
return DoubleElim.get_goal()
elif res.status_code == 503:
return Position(0, 0, -1), 503
response = json.loads(res.content)
logger.debug(f"DoubleElim.get_goal = {response}, status code = {res.status_code}")
return Position(response["x"], response["y"], -1), res.status_code
@staticmethod
def get_items() -> Tuple[List[Dict], int]:
"""Führt den /api/getItems Aufruf an die API aus.
:return: Eine Liste aller Items, die sich derzeit auf dem Spielfeld befinden. Items sind "dictionaries", die wie folgt aussehen: {"id": 0, "x": 0, "y": 0}
:rtype: Tuple[List[Dict], int]
"""
res = requests.get(API_URL_GET_ITEMS)
if res.status_code == 408:
logger.error(f"DoubleElim.get_items timeout. API={API_URL_GET_ITEMS}")
time.sleep(RETRY_TIMEOUT)
return DoubleElim.get_items()
elif res.status_code == 503:
return [], 503
response = json.loads(res.content)
logger.debug(f"DoubleElim.get_items = {response}, status code = {res.status_code}")
return response, res.status_code
@staticmethod
def get_scores() -> Tuple[Dict, int]:
"""Führt den /api/getScores Aufruf an die API aus.
:return: Ein "dictionary" mit dem eignen Score und dem des Gegners: {"self":2,"opponent":0}
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_SCORES)
if res.status_code == 408:
logger.error(f"DoubleElim.get_scores timeout. API={API_URL_GET_SCORES}")
time.sleep(RETRY_TIMEOUT)
return DoubleElim.get_scores()
elif res.status_code == 503:
return {"self": 0, "opponent": 0}, 503
response = json.loads(res.content)
logger.debug(f"DoubleElim.get_scores = {response}, status code = {res.status_code}")
return response, res.status_code

37
compLib/Encoder.py Normal file
View file

@ -0,0 +1,37 @@
import compLib.CompLib_pb2 as CompLib_pb2
from compLib.CompLibClient import CompLibClient
class Encoder(object):
"""Klasse zum Zugriff auf die Encoder der einzelnen Motoren
"""
@staticmethod
def read_all_positions():
"""Lesen aller absoluten Positionen der einzelnen Encoder
:return: Tupel mit allen aktuellen Encoderpositionen
"""
request = CompLib_pb2.EncoderReadPositionsRequest()
request.header.message_type = request.DESCRIPTOR.full_name
response = CompLib_pb2.EncoderReadPositionsResponse()
response.ParseFromString(CompLibClient.send(
request.SerializeToString(), request.ByteSize()))
return tuple(i for i in response.positions)
@staticmethod
def read_all_velocities():
"""Lesen der Geschwindigkeit aller angeschlossenen Motoren.
:return: Tupel aller aktuellen Motorgeschwindigkeiten in Radianten pro Sekunde
"""
request = CompLib_pb2.EncoderReadVelocitiesRequest()
request.header.message_type = request.DESCRIPTOR.full_name
response = CompLib_pb2.EncoderReadVelocitiesResponse()
response.ParseFromString(CompLibClient.send(
request.SerializeToString(), request.ByteSize()))
return tuple(i for i in response.velocities)

23
compLib/HealthCheck.py Normal file
View file

@ -0,0 +1,23 @@
import threading
import time
import compLib.CompLib_pb2 as CompLib_pb2
from compLib.CompLibClient import CompLibClient
class HealthUpdater(object):
started = False
@staticmethod
def start():
if not HealthUpdater.started:
threading.Thread(target=HealthUpdater.loop, daemon=True).start()
HealthUpdater.started = True
@staticmethod
def loop():
while True:
request = CompLib_pb2.HealthUpdateRequest()
request.header.message_type = request.DESCRIPTOR.full_name
CompLibClient.send(request.SerializeToString(), request.ByteSize())
time.sleep(0.25)

45
compLib/IRSensor.py Normal file
View file

@ -0,0 +1,45 @@
import time
import compLib.CompLib_pb2 as CompLib_pb2
from compLib.CompLibClient import CompLibClient
class IRSensor(object):
"""Ermöglicht den Zugriff auf die einzelnen IRSensoren des Roboters
"""
@staticmethod
def read_all():
"""Auslesen aller Sensoren gleichzeitig
:return: Array aller Sensorwerte
"""
request = CompLib_pb2.IRSensorsReadAllRequest()
request.header.message_type = request.DESCRIPTOR.full_name
response = CompLib_pb2.IRSensorsReadAllResponse()
response.ParseFromString(CompLibClient.send(
request.SerializeToString(), request.ByteSize()))
return [i for i in response.data]
@staticmethod
def enable():
"""Aktivieren Infrarot-Sender. Muss bei jedem Programmstart ausgeführt werden.
"""
request = CompLib_pb2.IRSensorsEnableRequest()
request.header.message_type = request.DESCRIPTOR.full_name
CompLibClient.send(request.SerializeToString(), request.ByteSize())
time.sleep(0.1) # IR sensor reading is async -> Wait a bit
@staticmethod
def disable():
"""Deaktivieren der Infrarot-Sender
"""
request = CompLib_pb2.IRSensorsDisableRequest()
request.header.message_type = request.DESCRIPTOR.full_name
CompLibClient.send(request.SerializeToString(), request.ByteSize())
time.sleep(0.1) # IR sensor reading is async -> Wait a bit

142
compLib/Motor.py Normal file
View file

@ -0,0 +1,142 @@
import compLib.CompLib_pb2 as CompLib_pb2
from compLib.CompLibClient import CompLibClient
MOTOR_COUNT = 4
class Motor(object):
"""Klasse zum Ansteuern der Motoren
"""
@staticmethod
def power(port: int, percent: float):
"""Motor auf eine prozentuale Leistung der Höchstgeschwindigkeit einstellen
:param port: Port, an welchen der Motor angesteckt ist. 0-3
:param percent: Prozentsatz der Höchstgeschwindigkeit. zwischen -100 und 100
:raises: IndexError
"""
if port < 0 or port >= MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
if percent < -100 or percent > 100:
raise IndexError(
"Invalid Motor speed specified! Speed is between -100 and 100 percent!")
request = CompLib_pb2.MotorSetPowerRequest()
request.header.message_type = request.DESCRIPTOR.full_name
request.port = port
request.power = percent
CompLibClient.send(request.SerializeToString(), request.ByteSize())
@staticmethod
def multiple_power(*arguments: tuple[int, float]):
"""Mehrere Motoren auf eine prozentuale Leistung der Höchstgeschwindigkeit einstellen
:param arguments: tuple von port, percentage
:raises: IndexError
"""
request = CompLib_pb2.MotorsSetPowerRequest()
request.header.message_type = request.DESCRIPTOR.full_name
for port, percent in arguments:
if port < 0 or port >= MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
if percent < -100 or percent > 100:
raise IndexError(
"Invalid Motor speed specified! Speed is between -100 and 100 percent!")
inner_request = CompLib_pb2.MotorSetPowerRequest()
inner_request.port = port
inner_request.power = percent
request.requests.append(inner_request)
CompLibClient.send(request.SerializeToString(), request.ByteSize())
@staticmethod
def speed(port: int, speed: float):
"""Geschwindigkeit des Motors einstellen
:param port: Port, an welchen der Motor angesteckt ist. 0-3
:param speed: Drehzahl, mit der sich ein Motor dreht, in Centimeter pro Sekunde (cm/s)
:raises: IndexError
"""
if port < 0 or port >= MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
request = CompLib_pb2.MotorSetSpeedRequest()
request.header.message_type = request.DESCRIPTOR.full_name
request.port = port
request.speed = speed
CompLibClient.send(request.SerializeToString(), request.ByteSize())
@staticmethod
def multiple_speed(*arguments: tuple[int, float]):
"""Geschwindigkeit mehrerer Motoren einstellen
:param arguments: tuple von port, Geschwindigkeit in Radianten pro Sekunde (rad/s)
:raises: IndexError
"""
request = CompLib_pb2.MotorsSetSpeedRequest()
request.header.message_type = request.DESCRIPTOR.full_name
for port, speed in arguments:
if port < 0 or port >= MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
inner_request = CompLib_pb2.MotorSetSpeedRequest()
inner_request.port = port
inner_request.speed = speed
request.requests.append(inner_request)
CompLibClient.send(request.SerializeToString(), request.ByteSize())
@staticmethod
def pulse_width(port: int, percent: float):
"""Setzen den Pulsbreite eines Motors in Prozent der Periode
:param port: Port, an welchen der Motor angesteckt ist. 0-3
:param percent: Prozent der Periode zwischen -100 und 100
:raises: IndexError
"""
if port < 0 or port >= MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
request = CompLib_pb2.MotorSetPulseWidthRequest()
request.header.message_type = request.DESCRIPTOR.full_name
request.port = port
request.percent = percent
CompLibClient.send(request.SerializeToString(), request.ByteSize())
@staticmethod
def multiple_pulse_width(*arguments: tuple[int, float]):
"""Setzen den Pulsbreite mehrerer Motoren in Prozent der Periode
:param arguments: tuple von port, prozent
:raises: IndexError
"""
request = CompLib_pb2.MotorsSetPulseWidthRequest()
request.header.message_type = request.DESCRIPTOR.full_name
for port, percent in arguments:
if port < 0 or port >= MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
inner_request = CompLib_pb2.MotorSetPulseWidthRequest()
inner_request.port = port
inner_request.percent = percent
request.requests.append(inner_request)
CompLibClient.send(request.SerializeToString(), request.ByteSize())

57
compLib/Movement.py Normal file
View file

@ -0,0 +1,57 @@
import compLib.CompLib_pb2 as CompLib_pb2
from compLib.CompLibClient import CompLibClient
class Movement(object):
"""High level class to control movement of the robot
"""
@staticmethod
def drive_distance(distance: float, speed: float):
"""
Drive a given distance with a certain speed.
Positive distance and speed with result in forward motion. Everything else will move backwards.
:param distance: Distance in meters
:param speed: Speed in meters per second
:return: None
"""
request = CompLib_pb2.DriveDistanceRequest()
request.header.message_type = request.DESCRIPTOR.full_name
request.distance_m = distance
request.velocity_m_s = speed
response = CompLib_pb2.GenericResponse()
response.ParseFromString(CompLibClient.send(request.SerializeToString(), request.ByteSize()))
@staticmethod
def turn_degrees(degrees: float, speed: float):
"""
Turn specified degrees with a given speed.
Positive degrees and speed with result in counter-clockwise motion. Everything else will be clockwise
:param degrees: Degrees between -180 and 180
:param speed: Speed in radians per second
:return: None
"""
request = CompLib_pb2.TurnDegreesRequest()
request.header.message_type = request.DESCRIPTOR.full_name
request.angle_degrees = degrees
request.velocity_rad_s = speed
response = CompLib_pb2.GenericResponse()
response.ParseFromString(CompLibClient.send(request.SerializeToString(), request.ByteSize()))
@staticmethod
def drive(linear: float, angular: float):
"""
Non-blocking way to perform a linear and angular motion at the same time.
:param linear: Linear speed in meters per second
:param angular: Angular speed in radians per second
:return: None
"""
request = CompLib_pb2.DriveDistanceRequest()
request.header.message_type = request.DESCRIPTOR.full_name
request.linear_velocity_m_s = linear
request.angular_velocity_rad_s = angular
response = CompLib_pb2.GenericResponse()
response.ParseFromString(CompLibClient.send(request.SerializeToString(), request.ByteSize()))

124
compLib/Seeding.py Normal file
View file

@ -0,0 +1,124 @@
import logging
import os
import numpy as np
# TODO: if set to competition mode, get the seed from the api
FORCE_SEED = int(os.getenv("FORCE_SEED", "-1"))
logger = logging.getLogger("complib-logger")
class Gamestate:
@staticmethod
def __set_random_seed(seed: int):
logger.debug(f"Seeding seed to: {seed}")
np.random.seed(seed)
@staticmethod
def __get_random_number(min: int, max: int):
return np.random.randint(256 ** 4, dtype='<u4', size=1)[0] % (max - min + 1) + min
def __str__(self) -> str:
return f"""Seed: {self.seed}
Heu Color: {self.heu_color}
Material Pairs: {self.material_pairs}
Material Zones: {self.materials}
Logistic Plan: {self.logistic_plan}
Logistic Centers: {self.logistic_center}"""
def __init__(self, seed: int):
"""
Erstellt den Seeding "Gamestate" für den angegebenen Seed.
:param seed: Seed welcher zum Erstellen des Gamestates benutzt werden soll.
"""
if FORCE_SEED == -1:
self.seed = seed
else:
print(f"Wettkampfmodus, zufälliger Seed wird verwendet: Seed={FORCE_SEED}")
self.seed = FORCE_SEED
logger.debug(f"Creating gamestate with seed: {self.seed}")
self.__set_random_seed(self.seed)
self.heu_color = self.__get_random_number(1, 2)
self.materials = [0, 0, 0, 0]
self.material_pairs = []
for i in range(0, 4):
num1 = self.__get_random_number(0, 3)
self.material_pairs.append([num1, num1])
while self.material_pairs[i][1] == num1:
self.material_pairs[i][1] = self.__get_random_number(0, 3)
flat = [item for sublist in self.material_pairs for item in sublist]
for i in range(0, 4):
self.materials[i] = flat.count(i)
self.logistic_plan = [0 for i in range(0, 21)]
self.logistic_center = [[0, 0, 0, 0] for i in range(0, 4)]
visited = [5, 5, 5, 5]
def __logistic_plan_generator(i: int):
drive_to = self.__get_random_number(0, 3)
for j in range(0, 4):
drive_to = (drive_to + j) % 4
if visited[drive_to] <= 0 or drive_to == self.logistic_plan[i - 1]:
continue
self.logistic_plan[i] = drive_to
visited[drive_to] -= 1
finished = True
for k in visited:
if k != 0:
finished = False
if finished and drive_to == 2:
visited[drive_to] += 1
continue
if finished:
return True
if i < len(self.logistic_plan):
if __logistic_plan_generator(i + 1):
return True
visited[drive_to] += 1
return False
self.logistic_plan[0] = 2
visited[2] -= 1
_ = __logistic_plan_generator(1)
self.logistic_plan[-1] = 2
for i in range(0, len(self.logistic_plan) - 1):
self.logistic_center[self.logistic_plan[i]][self.logistic_plan[i + 1]] += 1
self.logistic_plan = [x + 10 for x in self.logistic_plan]
logger.debug(f"Created gamesate: {str(self)}")
def get_heuballen(self) -> int:
"""
Die Funktion gibt entweder die Zahl "1" oder "2" zurück. Wenn die Funktion "1" zurückgibt, dann liegen die Heuballen auf den gelben Linien. Wenn die Funktion "2" zurückgibt, dann liegen sie auf den blauen Flächen.
:return: Gibt entweder die Zahl 1 oder 2 zurück.
"""
return self.heu_color
def get_logistic_plan(self) -> []:
"""
Die Funktion gibt den "Logistik Plan" zurück. Also die Reihenfolge, in welcher der Roboter die Logistik Zonen Abfahren muss, um die Pakete welche dort liegen zu sortieren.
:return: Eine Liste an Zahlen zwischen 10 und 13.
"""
return self.logistic_plan
def get_material_deliveries(self) -> [[]]:
"""
Die Funktion gibt die einzelnen "Material Lieferungen" zurück. Da der Roboter immer zwei Paare an Materialien anliefern muss, gibt die Funktion eine Liste an Material Paaren zurück. Die Materialien werden dabei durch ihre Zonen-ID representiert. Also Holz ist z.B. "0" und die Ziegelsteine sind "3".
:return: Eine Liste and Material Paaren.
"""
return self.material_pairs

7
compLib/__init__.py Normal file
View file

@ -0,0 +1,7 @@
import logging
import os
if os.getenv("DEBUG", "0") != "0":
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
else:
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)

View file

@ -0,0 +1,14 @@
#ifndef ROS_NODE_H
#define ROS_NODE_H
#include "rclcpp/rclcpp.hpp"
class CompLibNode : public rclcpp::Node
{
public:
CompLibNode();
};
#endif

View file

@ -0,0 +1,24 @@
#ifndef CONTROLS_H
#define CONTROLS_H
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/msg/interface_buttons.hpp"
#include "irobot_create_msgs/msg/lightring_leds.hpp"
class ButtonPressNode : public rclcpp::Node
{
public:
ButtonPressNode();
void bt1_wait();
void bt2_wait();
void kill();
private:
void result_callback(const irobot_create_msgs::msg::InterfaceButtons::SharedPtr result);
rclcpp::Subscription<irobot_create_msgs::msg::InterfaceButtons>::SharedPtr interface_buttons_subscriber_;
bool button1{false};
bool button2{false};
};
#endif

67
compLib/include/motor.h Normal file
View file

@ -0,0 +1,67 @@
#ifndef MOTOR_H
#define MOTOR_H
#include <thread>
#include <memory>
#include <geometry_msgs/msg/twist.hpp>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/action/drive_distance.hpp"
#include "irobot_create_msgs/action/drive_arc.hpp"
#include "irobot_create_msgs/action/rotate_angle.hpp"
class DriveDistNode : public rclcpp::Node
{
public:
DriveDistNode();
void drive_dist(float meters, float velocity);
void kill();
private:
void result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveDistance>::WrappedResult & result);
rclcpp_action::Client<irobot_create_msgs::action::DriveDistance>::SharedPtr drive_dist_action_;
bool processing;
};
class SetSpeedNode : public rclcpp::Node
{
public:
SetSpeedNode();
void drive(float speed);
void stop();
void kill();
private:
void set_speed(float speed);
void drive_loop(float speed);
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr speed_publisher_;
bool run = true;
std::thread t;
};
class RotateAngleNode : public rclcpp::Node
{
public:
RotateAngleNode();
void rotate_angle(float angle, float velocity);
void kill();
private:
void result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::RotateAngle>::WrappedResult & result);
rclcpp_action::Client<irobot_create_msgs::action::RotateAngle>::SharedPtr rotate_angle_action_;
bool processing;
};
class DriveArcNode : public rclcpp::Node
{
public:
DriveArcNode();
void drive_arc(float angle, float radius, float velocity, int direction=1);
void kill();
private:
void result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveArc>::WrappedResult & result);
rclcpp_action::Client<irobot_create_msgs::action::DriveArc>::SharedPtr drive_arc_action_;
bool processing;
};
#endif

View file

@ -0,0 +1,11 @@
#ifndef SEQUENCE_LOCK_H
#define SEQUENCE_LOCK_H
#include <mutex>
namespace SequenceLock
{
std::mutex m;
}
#endif

22
compLib/package.xml Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>comp_lib</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="matthias@todo.todo">matthias</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>rclcpp_action</depend>
<depend>std_msgs</depend>
<depend>irobot_create_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View file

@ -0,0 +1,10 @@
#include "ros_node.h"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
CompLibNode::CompLibNode()
: Node("CompLibNode")
{
}

53
compLib/src/controls.cpp Normal file
View file

@ -0,0 +1,53 @@
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/msg/interface_buttons.hpp"
#include "controls.h"
ButtonPressNode::ButtonPressNode()
: Node("button_press_node")
{
interface_buttons_subscriber_ = this->create_subscription<irobot_create_msgs::msg::InterfaceButtons>(
"/interface_buttons",
rclcpp::SensorDataQoS(),
std::bind(&ButtonPressNode::result_callback, this, std::placeholders::_1)
);
}
void ButtonPressNode::bt1_wait()
{
RCLCPP_INFO(this->get_logger(), "Wait for button 1...");
button1 = false;
while (!button1) {}
button1 = false;
}
void ButtonPressNode::bt2_wait()
{
RCLCPP_INFO(this->get_logger(), "Wait for button 2...");
button2 = false;
while (!button2) {}
button2 = false;
}
void ButtonPressNode::result_callback(const irobot_create_msgs::msg::InterfaceButtons::SharedPtr result)
{
if (result->button_1.is_pressed) {
button1 = true;
}
if (result->button_2.is_pressed) {
button2 = true;
}
if (result->button_power.is_pressed) {
}
}
void ButtonPressNode::kill()
{
RCLCPP_INFO(this->get_logger(), "ButtonPressNode killed");
rclcpp::shutdown();
}

93
compLib/src/main.cpp Normal file
View file

@ -0,0 +1,93 @@
#include <thread>
#include <memory>
#include <chrono>
#include <mutex>
#include "motor.h"
#include "controls.h"
std::mutex action_mutex;
// #lazyness
void run_node(std::shared_ptr<DriveDistNode> node)
{
rclcpp::spin(node);
}
void run_node1(std::shared_ptr<SetSpeedNode> node)
{
rclcpp::spin(node);
}
void run_node2(std::shared_ptr<RotateAngleNode> node)
{
rclcpp::spin(node);
}
void run_node3(std::shared_ptr<DriveArcNode> node)
{
rclcpp::spin(node);
}
void run_node4(std::shared_ptr<ButtonPressNode> node)
{
rclcpp::spin(node);
}
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto ddn = std::make_shared<DriveDistNode>();
auto ssn = std::make_shared<SetSpeedNode>();
auto ran = std::make_shared<RotateAngleNode>();
auto dan = std::make_shared<DriveArcNode>();
auto bpn = std::make_shared<ButtonPressNode>();
std::thread t;
std::thread t1;
std::thread t2;
std::thread t3;
std::thread t4;
t = std::thread(run_node, ddn);
t1 = std::thread(run_node1, ssn);
t2 = std::thread(run_node2, ran);
t3 = std::thread(run_node3, dan);
t4 = std::thread(run_node4, bpn);
bpn->bt1_wait();
bpn->bt2_wait();
bpn->bt1_wait();
ssn->drive(0.3);
std::this_thread::sleep_for (std::chrono::milliseconds(2000));
ssn->stop();
// std::this_thread::sleep_for (std::chrono::milliseconds(1000));
ran->rotate_angle(-45, 0.5);
// std::this_thread::sleep_for (std::chrono::milliseconds(5000));
ddn->drive_dist(0.2, 0.3);
// std::this_thread::sleep_for (std::chrono::milliseconds(5000));
dan->drive_arc(90, 0.5, 0.5);
ddn->kill();
ssn->kill();
ddn->kill();
dan->kill();
bpn->kill();
t.join();
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}

225
compLib/src/motor.cpp Normal file
View file

@ -0,0 +1,225 @@
#include <chrono>
#include <thread>
#include <mutex>
#include <future>
#include <memory>
#include <geometry_msgs/msg/twist.hpp>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/action/drive_distance.hpp"
#include "irobot_create_msgs/action/drive_arc.hpp"
#include "irobot_create_msgs/action/rotate_angle.hpp"
#include "motor.h"
#include "sequence_lock.h"
double pi = 2 * acos(0.0);
DriveDistNode::DriveDistNode()
: Node("drive_dist_node")
{
drive_dist_action_ = rclcpp_action::create_client<irobot_create_msgs::action::DriveDistance>(
this,
"/drive_distance"
);
}
void DriveDistNode::drive_dist(float meters, float velocity)
{
RCLCPP_INFO(this->get_logger(), "drive dist");
processing = true;
auto data = irobot_create_msgs::action::DriveDistance::Goal();
auto send_goal_options = rclcpp_action::Client<irobot_create_msgs::action::DriveDistance>::SendGoalOptions();
send_goal_options.result_callback =
std::bind(&DriveDistNode::result_callback, this, std::placeholders::_1);
data.distance = meters;
data.max_translation_speed = velocity;
drive_dist_action_->async_send_goal(data, send_goal_options);
while (processing) {}
}
void DriveDistNode::result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveDistance>::WrappedResult & result)
{
processing = false;
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "finished dist");
return;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
}
void DriveDistNode::kill()
{
RCLCPP_INFO(this->get_logger(), "DriveDistNode killed");
rclcpp::shutdown();
}
SetSpeedNode::SetSpeedNode()
: Node("set_speed_node")
{
speed_publisher_ = this->create_publisher<geometry_msgs::msg::Twist>(
"/cmd_vel",
rclcpp::SensorDataQoS()
);
}
void SetSpeedNode::drive_loop(float speed)
{
while (run)
{
set_speed(speed);
// sleep set as described at http://wiki.ros.org/Robots/TIAGo/Tutorials/motions/cmd_vel
std::this_thread::sleep_for (std::chrono::milliseconds(333));
}
}
void SetSpeedNode::set_speed(float speed)
{
auto data = geometry_msgs::msg::Twist();
data.linear.x = speed;
data.linear.y = 0;
data.linear.z = 0;
data.angular.x = 0;
data.angular.x = 0;
data.angular.x = 0;
speed_publisher_->publish(data);
}
void SetSpeedNode::drive(float speed)
{
RCLCPP_INFO(this->get_logger(), "Start drive");
run = true;
t = std::thread(&SetSpeedNode::drive_loop, this, speed);
}
void SetSpeedNode::stop()
{
run = false;
RCLCPP_INFO(this->get_logger(), "Stop drive");
}
void SetSpeedNode::kill()
{
RCLCPP_INFO(this->get_logger(), "SetSpeedNode killed");
rclcpp::shutdown();
}
RotateAngleNode::RotateAngleNode()
: Node("rotate_angle_node")
{
rotate_angle_action_ = rclcpp_action::create_client<irobot_create_msgs::action::RotateAngle>(
this,
"rotate_angle"
);
}
void RotateAngleNode::rotate_angle(float angle, float velocity)
{
processing = true;
angle *= pi / 180;
auto data = irobot_create_msgs::action::RotateAngle::Goal();
auto send_goal_options = rclcpp_action::Client<irobot_create_msgs::action::RotateAngle>::SendGoalOptions();
send_goal_options.result_callback =
std::bind(&RotateAngleNode::result_callback, this, std::placeholders::_1);
data.angle = angle;
data.max_rotation_speed = velocity;
rotate_angle_action_->async_send_goal(data, send_goal_options);
while (processing) {}
}
void RotateAngleNode::result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::RotateAngle>::WrappedResult & result)
{
processing = false;
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "finished rotation");
return;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
}
void RotateAngleNode::kill()
{
RCLCPP_INFO(this->get_logger(), "RotateAngleNode killed");
rclcpp::shutdown();
}
DriveArcNode::DriveArcNode()
: Node("drive_arc_node")
{
drive_arc_action_ = rclcpp_action::create_client<irobot_create_msgs::action::DriveArc>(
this,
"/drive_arc"
);
}
void DriveArcNode::drive_arc(float angle, float radius, float velocity, int direction)
{
processing = true;
angle *= pi / 180;
auto data = irobot_create_msgs::action::DriveArc::Goal();
auto send_goal_options = rclcpp_action::Client<irobot_create_msgs::action::DriveArc>::SendGoalOptions();
send_goal_options.result_callback =
std::bind(&DriveArcNode::result_callback, this, std::placeholders::_1);
data.angle = angle;
data.radius = radius;
data.translate_direction = direction;
data.max_translation_speed = velocity;
drive_arc_action_->async_send_goal(data, send_goal_options);
while (processing) {}
}
void DriveArcNode::result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveArc>::WrappedResult & result)
{
processing = false;
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "finished arc");
return;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
}
void DriveArcNode::kill()
{
RCLCPP_INFO(this->get_logger(), "DriveArcNode killed");
rclcpp::shutdown();
}

View file

@ -1,3 +0,0 @@
#!/bin/bash
scp -r compLIB pi@10.20.85.225:/home/pi/compLIB

4
docs/.gitignore vendored
View file

@ -1 +1,3 @@
build
build
logs.db
!lib

View file

View file

@ -15,15 +15,17 @@ import sys
sys.path.insert(0, os.path.abspath('../..'))
sys.setrecursionlimit(1500)
os.environ["EXTENSIVE_LOGGING"] = "False"
# -- Project information -----------------------------------------------------
project = 'CompLib'
copyright = '2021, robo4you'
copyright = '2022, Verein zur Förderung von Wissenschaft und Technik an Schulen (F-WuTS)'
author = 'robo4you'
autoclass_content = 'both'
# The full version, including alpha/beta/rc tags
release = '0.0.1'
release = '0.2.3'
# -- General configuration ---------------------------------------------------
@ -35,7 +37,8 @@ extensions = [
'sphinx_rtd_theme'
]
autodoc_mock_imports = ["smbus", "compLIB.PCA9685", "RPi"]
autodoc_mock_imports = ["smbus", "compLib.PCA9685", "RPi",
"pigpio", "flask", "apt", "influxdb_client"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@ -56,3 +59,11 @@ html_theme = 'sphinx_rtd_theme'
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_logo = "images/compair-logo-white.svg"
html_theme_options = {
'logo_only': True,
'display_version': False,
}
language = "de"

32
docs/source/faq.rst Normal file
View file

@ -0,0 +1,32 @@
FAQ
###
Was ist das Passwort für die Entwicklungsumgebung?
--------------------------------------------------
``compair``
Wie verbinde ich mich zur Entwicklungsumgebung?
-----------------------------------------------
See :ref:`gettingstarted_codeserver`
Was ist der Benutzername und das Passwort für den Raspberry Pi?
---------------------------------------------------------------
``compair`` ``compair``
Wie aktualisiere ich meine Software?
------------------------------------
.. code-block:: bash
sudo apt update
sudo apt upgrade
sudo update-firmware
Wie kann ich die SD-Karte neu beschreiben?
------------------------------------------
`SD-Karten Image <https://drive.google.com/drive/folders/16lMe-yGphk947L4WPjd4oD8ndY9R1WbA?usp=share_link>`_
Software zum Schreiben der SD-Karte `balenaEtcher <https://www.balena.io/etcher/>`_

View file

@ -0,0 +1,11 @@
.. _gettingstarted_codeserver:
Programmierumgebung
###################
Als Umgebung zur Programmierung des Roboters wird `code-server <https://github.com/coder/code-server>`_ eingesetzt, welche bereits am Roboter vorinstalliert ist.
Verbindung zur Entwicklungsumgebung herstellen
----------------------------------------------
Am Roboter wird die IP-Adresse des Raspberry Pi angezeigt. Um nun die Verbindung herzustellen, muss man in einem Web-Browser einfach ``<roboter_ip>:8080`` eingeben.
Das Passwort für Visual Studio Code im Browser ist ``compair``!

View file

@ -0,0 +1,26 @@
Mein erstes Programm
####################
Um mit der Programmierung zu beginnen, müssen wir zunächst einen neuen Ordner erstellen, in dem alle unsere Python-Dateien gespeichert werden.
|codeServerFolder|
Sie können diesen Ordner nennen, wie Sie wollen, für dieses Beispiel heißt er ``compAIR``.
Im nächsten Schritt erstellen wir unsere Datei ``main.py``.
|codeServerFile|
Dann können wir beginnen, unseren Code in diese Datei zu schreiben.
.. code-block:: python
print("Hallo Welt")
Praktischerweise können wir die Datei auch über die VS-Code-Plattform ausführen.
|codeServerRun|
Dann öffnet sich ein Terminal, der die Ausgabe unseres Programms anzeigt.
|codeServerTerminal|
.. |codeServerFolder| image:: images/06_codeServerFolder.png
.. |codeServerFile| image:: images/03_codeServerFile.png
.. |codeServerRun| image:: images/04_codeServerRun.png
.. |codeServerTerminal| image:: images/05_codeServerTerminal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View file

@ -0,0 +1,12 @@
Erste Schritte
##############
.. toctree::
:maxdepth: 5
wifi.rst
codeServer.rst
firstProgram.rst
update.rst
secondProgram.rst
thridProgram.rst

View file

@ -0,0 +1,313 @@
Mein zweites Programm
#####################
Motoren ansteuern
-----------------
Um die Motoren des Roboters zu steuern, müssen wir zunächst das entsprechende Python-Modul am Anfang der Datei importieren. Dann können wir Motor.power(port, power) verwenden, um den Motor zu steuern.
Dies ist auch ein guter Punkt, um sich mit der Dokumentation vertraut zu machen: Besuchen wir https://lib.comp-air.at/lib/Motor.html#compLib.Motor.Motor.power. Hier werden die beiden relevanten Parameter beschrieben.
Als Beispiel wollen wir den rechten Motor für fünf Sekunden auf volle Geschwindigkeit setzen:
.. code-block:: python
:linenos:
# motor.py
import time
from compLib.Motor import Motor
Motor.power(0, 100)
time.sleep(5)
Gerade fahren
-------------
Um geradeaus zu fahren, müssen wir beide Motoren auf dieselbe Geschwindigkeit einstellen.
Aber Achtung! Der rechte Motor muss umgedreht werden! Das liegt daran, dass einer nach rechts und einer nach links zeigt, sie sind also technisch gesehen gespiegelt.
Wenn wir nun diesen Code ausführen, wird der Roboter 5 Sekunden lang vorwärts fahren:
.. code-block:: python
:linenos:
# motor.py
import time
from compLib.Motor import Motor
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(5)
**Erläuterung**
| In ``Zeile 2`` wird das python-Paket ``time`` importiert. Wir brauchen es später, um auf die Fahrt des Roboters zu warten. Z.B.: ``time.sleep(5)``
| In ``Zeile 3`` importieren wir die notwendigen Funktionen aus dem ``Motor``-Modul der compLib.
| In ``Zeile 5`` stellen wir den ``rechten`` Motor so ein, dass er vorwärts fährt. Da der Motor rückwärts eingebaut ist, müssen wir den Wert auf ``-100`` setzen.
| In ``Zeile 6`` stellen wir den ``linken`` Motor auf Vorwärtsfahrt ein. Hier können wir den Wert ``100`` verwenden, da der Motor in der richtigen Richtung eingebaut ist.
| In ``Zeile 7`` müssen wir warten, bis der Roboter die Fahrbefehle tatsächlich ausführt. In diesem Fall warten wir ``5`` Sekunden lang.
Danach wird das Programm beendet und der Roboter bleibt stehen.
Mehr fahren
+++++++++++
Jetzt ist es Zeit für einige komplexere Bewegungen. Um unseren Code modular und leicht lesbar zu halten, werden wir jede Aktion in eine eigene Funktion packen.
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward():
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(2)
In ``Zeile 4`` definieren wir die Funktion ``driveForward()``, die den Roboter mit voller Geschwindigkeit zwei Sekunden vorwärts bewegt.
Jetzt werden wir eine Funktion für das Rückwärtsfahren definieren:
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward():
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(2)
def driveBackward():
Motor.power(0, 100)
Motor.power(3, -100)
time.sleep(2)
In ``Zeile 9`` haben wir die Funktion ``driveBackward()`` definiert, die den Roboter zwei Sekunden lang rückwärts fahren lässt.
Jetzt können wir diese beiden Funktionen aufrufen und vorwärts und dann wieder rückwärts fahren:
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward():
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(2)
def driveBackward():
Motor.power(0, 100)
Motor.power(3, -100)
time.sleep(2)
driveForward()
driveBackward()
Wenn wir diesen Code ausführen, sollte der Roboter zunächst zwei Sekunden vorwärts und dann wieder zwei Sekunden rückwärts fahren und ungefähr an der gleichen Position wie beim Start anhalten.
Zwischen den Zeilen ``14`` und ``15`` brauchen wir kein ``time.sleep(2)``, da der sleep-Befehl bereits in den Funktionen integriert ist.
Jetzt wollen wir, dass der Roboter erst vorwärts fährt, dann zwei Sekunden stillsteht und dann wieder rückwärts in seine Ausgangsposition fährt.
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward():
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(2)
def driveBackward():
Motor.power(0, 100)
Motor.power(3, -100)
time.sleep(2)
driveForward()
time.sleep(2)
driveBackward()
Wenn wir den obigen Code ausführen, bleibt der Roboter nicht zwei Sekunden lang stehen, sondern fährt nach der Funktion ``driveForward()`` noch zwei Sekunden lang weiter. Warum passiert das? Um das zu verstehen, müssen wir wie der Roboter denken!
**Erläuterung**
| 1. (``Zeile 14``) Die Funktion Vorwärtsfahrt wird aufgerufen
| (``Zeile 5``) Motor 1 wird auf -100 gesetzt
| (``Zeile 6``) Motor 4 wird auf 100 gesetzt
| (``Zeile 7``) Zwei Sekunden warten und Motor 1 mit der Geschwindigkeit -100 und Motor 4 mit der Geschwindigkeit 100 bewegen (z.B. vorwärts fahren)
| 2. (``Zeile 15``) Zwei Sekunden warten, die Motoren sind immer noch auf -100 und 100 eingestellt, also fahren wir weiter vorwärts
| 3. (``Zeile 16``) Die Funktion Rückwärtsfahren wird aufgerufen
| (``Zeile 5``) Motor 1 wird auf 100 gesetzt
| (``Zeile 6``) Motor 4 wird auf -100 gesetzt
| (``Zeile 7``) Warte zwei Sekunden und bewege Motor 1 mit der Geschwindigkeit 100 und Motor 4 mit der Geschwindigkeit -100 (z.B. Rückwärtsfahren)
| 4. Das Programm ist beendet, und alle Motordrehzahlen werden auf 0 gesetzt.
Wir sehen also, dass wir die Motoren nach der Vorwärts- oder Rückwärtsfunktion wieder auf Geschwindigkeit ``0`` setzen müssen, wenn wir den Roboter anhalten wollen. Für diesen Anwendungsfall können wir eine neue Funktion ``stopMotors()`` schreiben, die die Geschwindigkeit für Motor ``0`` und ``3`` auf ``0`` setzt:
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward():
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(2)
def driveBackward():
Motor.power(0, 100)
Motor.power(3, -100)
time.sleep(2)
def stopMotors():
Motor.power(0, 0)
Motor.power(3, 0)
Wenn wir nun vorwärts fahren, dann zwei Sekunden warten und dann wieder rückwärts fahren wollen, können wir die Funktionen wie folgt aufrufen:
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward():
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(2)
def driveBackward():
Motor.power(0, 100)
Motor.power(3, -100)
time.sleep(2)
def stopMotors():
Motor.power(0, 0)
Motor.power(3, 0)
driveForward()
stopMotors()
time.sleep(2)
driveBackward()
Und endlich bekommen wir die Bewegung, die wir uns wünschen.
**More Optimizations**
Während der Code für sehr einfache Bewegungen funktioniert, wollen wir normalerweise nicht, dass unsere Funktionen entscheiden, wie lange wir vorwärts fahren. Vielleicht müssen wir manchmal vier Sekunden vorwärts fahren, und manchmal nur eine Sekunde.
Nehmen wir an, wir wollen vier Sekunden vorwärts fahren. Wir wissen, dass ``driveForward()`` den Roboter zwei Sekunden vorwärts bewegen wird. Also können wir die Funktion einfach zwei Mal aufrufen!
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward():
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(2)
driveForward()
driveForward()
Was aber, wenn wir uns nur eine Sekunde vorwärts bewegen wollen? Oder vielleicht drei Sekunden? Mit der Funktion ``driveForward()`` können wir das im Moment nicht machen.
Stattdessen werden wir die Funktion so umschreiben, dass sie einen Parameter akzeptiert, der die Zeit angibt.
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward(seconds):
Motor.power(0, -100)
Motor.power(3, 100)
time.sleep(seconds)
driveForward(3)
Und mit dieser neuen Funktion können wir drei Sekunden lang vorwärts fahren.
Wie funktioniert das nun?
In ``Zeile 4`` definieren wir die Funktion ``driveForward`` und sagen, dass sie einen Parameter ``seconds`` benötigt. Dieser Parameter ist im Grunde eine Variable, die wir uns zum Zeitpunkt der Definition wie einen Platzhalter vorstellen können. Wenn wir die Funktion definieren, wissen wir noch nicht, welchen Wert ``seconds`` haben wird.
Später in ``Zeile 9``, wenn wir die Funktion aufrufen, übergeben wir den Wert ``3`` an die Funktion und unser Platzhalter ``seconds`` wird den Wert ``3`` haben. Der Roboter wird also drei Sekunden vorwärts fahren.
Vielleicht wollen wir auch, dass der Roboter mit verschiedenen Geschwindigkeiten fahren kann. Wir können also einen weiteren Parameter mit dem Namen ``speed`` anlegen. Dann werden wir ein Programm schreiben, das den Roboter drei Sekunden mit voller Geschwindigkeit und dann fünf Sekunden mit halber Geschwindigkeit fahren lässt.
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward(seconds, speed):
Motor.power(0, -speed)
Motor.power(3, speed)
time.sleep(seconds)
driveForward(3, 100)
driveForward(5, 50)
In ``Zeile 9`` wird der Platzhalter ``seconds`` auf ``3`` und die ``Geschwindigkeit`` auf ``100`` gesetzt.
In ``Zeile 10`` wird der Platzhalter ``seconds`` auf ``5`` und die ``Geschwindigkeit`` auf ``50`` gesetzt.
**Bewährte Praktiken**
Nun werden wir uns einige weitere Optimierungen und bewährte Verfahren ansehen.
**1. Wir sollten den Schlafbefehl nicht in die Fahrfunktion einbauen.**
Wir haben das bis jetzt getan, um ein Gefühl dafür zu bekommen, wie Funktionen funktionieren, und der Einfachheit halber. Später, wenn Sie anfangen, komplexere Programme zu schreiben, sollten Sie dies vermeiden.
Das Beispiel von oben, in dem wir vorwärts und rückwärts gefahren sind und zwei Sekunden gewartet haben, sollte also wie folgt aussehen:
.. code-block:: python
:linenos:
import time
from compLib.Motor import Motor
def driveForward(speed):
Motor.power(0, -speed)
Motor.power(3, speed)
def driveBackward(speed):
Motor.power(0, speed)
Motor.power(3, -speed)
def stopMotors():
Motor.power(0, 0)
Motor.power(3, 0)
driveForward(100) # Set the motors to forward
time.sleep(2) # Let the robot drive for 2 seconds
stopMotors() # Now stop the robot
time.sleep(2) # Wait another 2 seconds, robot is not moving
driveBackward(100) # Now set the motors to a backwards speed
time.sleep(2) # Let the robot continue driving for 2 seconds
stopMotors() # And finally stop it again
**Warum ist das so wichtig?**
Normalerweise schlafen wir nicht sehr viel und führen in dieser Zeit andere Verarbeitungen durch. Zum Beispiel könnten wir ein Bild von der Kamera verarbeiten oder die IR-Sensoren auslesen. Wenn wir also eine Funktion wie ``driveForward()`` aufrufen, können wir davon ausgehen, dass sie im Hintergrund abläuft und wir andere Aufgaben erledigen, während sich der Roboter bewegt, anstatt nur darauf zu warten, dass er fertig wird.
**2. Fahren Sie nicht zu langsam.**
Wenn du die Fahrgeschwindigkeit auf eine sehr kleine Zahl einstellst, kann es sein, dass sich der Roboter gar nicht mehr bewegt, weil die Motoren eine bestimmte Menge an Energie benötigen, um den Roboter überhaupt zu bewegen.
**3. Fahren Sie nicht zu schnell.**
Wenn du die Fahrgeschwindigkeit auf eine sehr hohe Zahl einstellst (z. B. ``100``), könnte dein Roboter zu schnell für seine Sensoren sein. Dies wird später wichtig sein, wenn wir versuchen, eine schwarze Linie zu erkennen, aber zu schnell über sie fahren.

View file

@ -0,0 +1,70 @@
Mein drittes Programm
#####################
Der offizielle compAIR-Bot ist mit einer Reihe von Sensoren ausgestattet. Die wichtigsten sind die Infrarotsensoren und -sender, die an der Vorderseite des Roboters angebracht sind. Insgesamt gibt es fünf IR-Sensoren.
Um loszulegen, muss man zunächst das entsprechende Modul wie folgt importieren:
.. code-block:: python
:linenos:
from compLib.IRSensor import IRSensor
|irSensor|
Wie im obigen Diagramm zu sehen ist, verfügt jeder Sensor auch über einen entsprechenden IR-Sender / Emitter. Dieser Sender kann mit ``IRSensor.set(port, enable)`` aktiviert werden.
Schalten wir nun alle fünf Sender ein:
.. code-block:: python
:linenos:
from compLib.IRSensor import IRSensor
IRSensor.enable()
Diese fünf verschiedenen Sensoren befinden sich an der Vorderseite des Roboters und sind wichtig, um schwarze Linien zu erkennen.
Es ist sehr einfach, den Wert der Sensoren abzulesen:
.. code-block:: python
:linenos:
from compLib.IRSensor import IRSensor
IRSensor.enable()
if IRSensor.read_all()[0] > 500:
print("high")
else:
print("low")
**Erkennen einer schwarzen Linie**
Um den IR-Sensor zu testen, kannst du deinen Roboter auf eine schwarze Linie stellen. Der Sensor in der Mitte sollte auf der schwarzen Linie liegen.
.. code-block:: python
:linenos:
from compLib.IRSensor import IRSensor
IRSensor.enable()
COLOR_BREAK = 900
if IRSensor.read_all()[2] > COLOR_BREAK:
print("Robot is standing on a black line")
else:
print("Robot is NOT standing on a black line")
Wenn das Programm ausgeführt wird, zeigt es an, dass der Roboter auf einer schwarzen Linie steht, wenn sich der mittlere IR-Sensor des Roboters über einer schwarzen Linie befindet, und es zeigt an, dass der Roboter NICHT auf einer schwarzen Linie steht, wenn sich der mittlere IR-Sensor nicht über einer Linie befindet.
| In ``Zeile 1`` importieren wir das ``IRSensor``-Modul, das zur Kommunikation mit dem IR-Sensor-Board verwendet werden kann.
| In ``Zeile 3`` wird der Sensor mit der Nummer ``3`` aktiviert. Wenn wir einen Sensor nicht aktivieren, können wir ihn nicht in unserem Programm verwenden.
| In ``Zeile 4`` stellen wir einen Farbschwellenwert von ``900`` ein, mit dem wir später prüfen werden, ob der Sensorwert unter oder über diesem Schwellenwert liegt. Unterhalb bedeutet, dass sich eine helle Farbe unter dem IR-Sensor befindet und ein höherer Wert als ``900`` bedeutet, dass sich eine dunkle Farbe unter dem IR-Sensor befindet.
| In ``Zeile 6`` lesen wir den Sensor Nummer ``2`` aus und prüfen, ob der Wert über dem von uns definierten Schwellenwert von ``900`` liegt. Wenn das der Fall ist, hat der IR-Sensor eine schwarze Linie erkannt.
Wir werden nun das Programm so ändern, dass es alle ``0.1`` Sekunden prüft, ob sich eine schwarze Linie unter dem Roboter befindet, und wenn dies der Fall ist, eine Meldung ausgibt.
.. |irSensor| image:: images/07_irSensor.webp

View file

@ -0,0 +1,13 @@
Software Updaten
#################
Da wir die ``compLib``, und die andere Software, welche auf dem Roboter läuft, laufend weiterentwickeln, solltet ihr immer wieder euren Roboter auf die neuste Version updaten. Dazu müsst ihr einfach den Roboter mit dem Internet verbinden und dann diesen Befehl in der Kommandozeile des Roboters eingeben:
.. code-block:: bash
sudo apt update && sudo apt upgrade
Am einfachsten kann das über die Webseite gemacht werden, auf der ihr auch euren Code schreibt. Dazu müsst ihr einfach nur das Terminal (= Konsole) öffnen, dann den Befehl dort hineinkopieren und Enter drücken.
|updatePic|
.. |updatePic| image:: images/09_update.png

View file

@ -0,0 +1,86 @@
.. _gettingStarted_wifi:
WLAN-Verbindung herstellen
##########################
Schritt für Schritt - macOS
---------------------------
1. SD-Karte aus dem Raspberry Pi bzw. Roboter entnehmen.
2. Einstecken der SD-Karte in den Computer
3. Öffnen der SD-Karte mit dem Namen "boot" |bootImage|
4. Generieren des PSK auf `https://www.wireshark.org/tools/wpa-psk.html <https://www.wireshark.org/tools/wpa-psk.html>`_ |pskImage|
5. Öffnen der Datei "wpa_supplicant.conf" auf der SD-Karte
6. Einfügen der Konfiguration. Dabei muss die SSID und der vorher generierte PSK eingesetzt werden ::
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=AT
network={
ssid="EinTollerNameFürDasWlan"
psk="98117b165a48f25cbe36f288ddf597729a40feeea93054c19bfa8e5eab238541"
}
7. Speichern, Auswerfen und wieder in den Raspberry Pi einbauen
8. Starten des Roboters
9. Die IP-Adresse sollte nun am Roboter angezeigt werden
.. |bootImage| image:: images/01_boot.png
.. |pskImage| image:: images/02_psk.png
Weitere Informationen
---------------------
Die "wpa_supplicant.conf" Datei wird beim Start des Rpasberry Pi automatisch an den richtigen Ort kopiert, damit sich der Roboter zum Wlan verbindet.
Eine genauere Anleitung wird vom Hersteller des Raspberry Pi `hier <https://www.raspberrypi.com/documentation/computers/configuration.html#configuring-networking-2>`_ bereitgestellt.
Windows......
-------------
Je nach Betriebssystem und Editor, mit dem Sie die Datei erstellen, könnte die Datei falsche Zeilenumbrüche oder eine falsche Dateierweiterung haben; stellen Sie also sicher, dass Sie einen Editor verwenden, der dies berücksichtigt. Linux erwartet das Zeilenumbruchzeichen LF (Line Feed).
Beispielsweise kann `Notepad++ <https://notepad-plus-plus.org/downloads/>`_ verwendet werden, um die Datei richtig zu speichern.
|notepadImage|
.. |notepadImage| image:: images/08_notepad.png
Fehlerbehandlung
----------------
Sollte es dazu kommen, dass der Roboter nicht automatisch die Verbindung mit dem Netzwerk herstellt, kann eine Kabelgebundene Verbindung zur Diagnose von Fehlern genutzt werden.
Dabei wird automatisch die IP-Adresse der Verbindung "eth" am Roboter angezeigt. Nach der erfolgreichen Verbindung zum Roboter mittels SSH kann die "wpa_cli" zur Fehlerbehandlung verwendet werden:
::
> wpa_cli
wpa_cli v2.9
Copyright (c) 2004-2019, Jouni Malinen <j@w1.fi> and contributors
This software may be distributed under the terms of the BSD license.
See README for more details.
Selected interface 'p2p-dev-wlan0'
Interactive mode
> interface wlan0
Connected to interface 'wlan0.
> scan
OK
<3>CTRL-EVENT-SCAN-STARTED
<3>CTRL-EVENT-SCAN-RESULTS
> scan_result
bssid / frequency / signal level / flags / ssid
68:02:b8:0c:d7:47 2462 -66 [WPA2-PSK-CCMP][ESS] WG
68:02:b8:0c:d7:40 5220 -63 [WPA2-PSK-CCMP][ESS] WG
34:2c:c4:da:dd:b9 5200 -65 [WPA-PSK-TKIP][WPA2-PSK-CCMP][WPS][ESS] WLAN10573403
98:da:c4:e5:21:d0 2437 -57 [WPA2-PSK-CCMP][ESS] WG
34:2c:c4:da:dd:c6 2412 -52 [WPA-PSK-][WPA2-PSK-CCMP+TKIP][WPS][ESS] WLAN10573403
20:83:f8:07:5b:90 2467 -67 [WPA2-PSK-CCMP][WPS][ESS] A1-075b8c
7c:39:53:94:49:82 5280 -77 [WPA2-PSK-CCMP][WPS][ESS] A1-944980-5G
7c:39:53:94:49:81 2427 -68 [WPA2-PSK-CCMP][WPS][ESS] A1-944980
90:fd:73:ac:d3:27 2452 -72 [WPA2-PSK-CCMP][WPS][ESS] Drei_H288A_24G_eKy5
50:e0:39:3c:e5:80 5180 -82 [WPA2-PSK-CCMP][WPS][ESS] A1-393CE57F
90:fd:73:ac:d3:28 5500 -83 [WPA2-PSK-CCMP][WPS][ESS] Drei_H288A_5G_eKy5
68:02:b8:41:42:f9 5180 -84 [WPA-PSK-TKIP][WPA2-PSK-CCMP][WPS][ESS] WLAN18792472
34:2c:c4:30:3c:65 5180 -89 [WPA-PSK-TKIP][WPA2-PSK-CCMP][WPS][ESS] witt
fa:0d:ac:d3:40:d4 2467 -80 [WPA2-PSK-CCMP][WPS][ESS][P2P] DIRECT-d4-HP M28 LaserJet
0e:84:dc:14:ac:27 2467 -85 [WPA2-PSK-CCMP][WPS][ESS][P2P] DIRECT-wo-BRAVIA
>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 188 KiB

View file

@ -1,25 +1,20 @@
Welcome to CompLib's documentation!
===================================
Dokumentation des Roboters
##########################
.. toctree::
:maxdepth: 2
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
Table of Contents
^^^^^^^^^^^^^^^^^
Inhalt
******
.. toctree::
:maxdepth: 5
:maxdepth: 1
:glob:
self
lib/*
gettingStarted/index.rst
software/installation.rst
faq.rst
other/usage
lib/index.rst
other/hardware.rst

View file

@ -0,0 +1,13 @@
.. _lib_doubleElim:
Double Elimination
*******************
Dokumentation des Double Elimination Moduls
============================================
.. autoclass:: compLib.DoubleElimination.Position
:members:
.. autoclass:: compLib.DoubleElimination.DoubleElim
:members:

View file

@ -0,0 +1,10 @@
.. _lib_encoder:
Encoder
*******
Dokumentation der Klasse
========================
.. autoclass:: compLib.Encoder.Encoder
:members:

View file

@ -0,0 +1,10 @@
.. _lib_irsensor:
Infrarot Sensoren
*****************
Dokumentation der Klasse
========================
.. autoclass:: compLib.IRSensor.IRSensor
:members:

View file

@ -0,0 +1,50 @@
.. _lib_motor:
Motoren
********
Dokumentation der Klasse
========================
.. autoclass:: compLib.Motor.Motor
:members:
Genauere Informationen
======================
Power vs Speed vs PulseWidth
-----------------------------
Zur ansteuerung der Motoren kann entweder ``Motor.power(...)``, ``Motor.speed(...)`` oder ``Motor.pulse_width(...)``` verwendet werden.
Der Unterschied der 3 Funktionen liegt dabei in der Einheit des 2. Parameters.
| Bei ``Motor.power()`` wird dabei ein Wert zwischen -100% und 100% der maximalen Geschwindigkeit angegeben.
| ``Motor.speed()`` verwendet die Encoder um die Geschwindigkeit der Motoren mittels closed-loop zu steuern. Diese Funktion sollte nur verwendet werden, wenn ``Motor.power()`` nicht zur Ansteuerung ausreicht.
| ``Motor.pulse_width()`` stellt die Geschwindigkeit des Motors mittels der Pulsbreite der PWM-Steuerung des Motors ein. Diese Funktion ist so nah an der Hardware wie möglich und sollte auch nur verwendet werden, wenn es einen expliziten Grund dafür gibt.
Normal vs Multiple
------------------
Der Aufruf der funktionen kann entweder über ``Motor.power(port, percent)`` oder ``Motor.power((port, percent), (port, percent), ..)`` erfolgen.
Der zweite Aufruf ermöglicht dem Entwickler dabei beide Motoren in einem Aufruf anzusteuern und bringt einen kleinen Vorteil in der Leistungsfähigkeit der Software.
Beispiele
=========
Vorwärts fahren
---------------
Mit folgenden Programm drehen sich beide Motoren mit 50% ihrer maximalen Geschwindigkeit.
Dabei ist zu beachten, dass ein Motor in die entgegengesetzte Richtung zum aneren Motor gedreht werden muss, da diese spiegelverkehrt montiert sind.
Zusätzlich ist ein ``time.sleep(5)`` notwendig, welches das Programm für 5 Sekunden pausiert. Diese Pause wird benötigt, da der Roboter automatisch alle Motoren beim Ende des Progammes deaktiviert.
.. code-block:: python
from compLib.Motor import Motor
import time
Motor.power(0, -50)
Motor.power(3, 50)
time.sleep(5)

View file

@ -0,0 +1,79 @@
.. _lib_camera:
Camera und OpenCV
*******************
Dokumentation des Camera Moduls
================================
.. autoclass:: compLib.Camera.Marker
:members:
.. autoclass:: compLib.Camera.Camera
:members:
Beispiele
=========
Bild Anzeigen
---------------
Das folgende Programm fragt Bilder von der Kamera ab und schickt sie an den Webserver, der im Hintergrund läuft. Der Benutzer kann dann auf die Webseite: http://raspi_ip:9898 gehen und die Ausgabe der Kamera sehen.
.. code-block:: python
from compLib.Camera import *
camera = Camera()
while True:
image = camera.get_frame()
camera.publish_frame(image)
ArUco Marker Erkennen
------------------------
In diesem Programm werden die ArUco Marker, die sich am Spielfeld befinden, erkannt. Diese "QR-Code" ähnlichen Marker finden sich in den Logistikzonen und können dazu verwendet werden zu erkennen, wo der Roboter hinfahren sollt etc.
.. code-block:: python
from compLib.Camera import *
camera = Camera()
while True:
image = camera.get_frame()
markers, image = camera.detect_markers_midpoint(image)
print(markers)
print("-----------------")
camera.publish_frame(image)
Hier ist z.B. der ArUco Marker mit der ID 0. Führe das Programm aus und lass den Roboter auf den Bildschirm schauen. Das Programm sollte die 2D Position ausgeben, welcher der ArUco Marker (genauer sein Mittelpunkt) im Camera Bild hat.
|ArucoExample|
.. |ArucoExample| image:: images/6x6_1000-0.png
Um die Positionen zu verarbeiten, muss dann nur noch das "markers" array durchgegangen werden. Das könnte z.B. so gemacht werden:
.. code-block:: python
from compLib.Camera import *
camera = Camera()
while True:
image = camera.get_frame()
markers, image = camera.detect_markers_midpoint(image)
print(markers)
print("-----------------")
for marker in markers:
print(f"Marker mit der id: {marker.id}")
print(f"Ist auf der X Position: {marker.x}")
print(f"und auf der Y Position: {marker.y}")
print("-----------------")
camera.publish_frame(image)
Wichtig ist noch zu beachten, dass die X und Y Positionen ihren Ursprung in der linken oberen Ecke des Bildes haben. D.h. die Position (0,0) ist im oberen linken Bildrand.

View file

@ -0,0 +1,36 @@
.. _lib_seeding:
Seeding
*******
Dokumentation des Seeding Moduls
================================
.. autoclass:: compLib.Seeding.Gamestate
:members:
Beispiele
----------
| In ``Zeile 1`` wird das Seeding Modul importiert.
| In ``Zeile 2`` definieren wir dann eine Variable, in der wir den "Seed" des Gamestates den wir erstellen wollten speichern.
| In ``Zeile 3`` erstellen wir dann einen neuen Gamestate mit dem Seed und speichern ihn in die Variable ``gamestate``.
| In ``Zeile 4`` geben wir dann den Gamestate aus, damit wir ihn auf der Konsole ansehen können.
.. code-block:: python
import compLib.Seeding as Seeding
seed = 42
gamestate = Seeding.Gamestate(seed)
print(gamestate)
In der Ausgabe des Print Statements sehen wir den generierten Gamestate.
.. code-block::
Seed: 42
Heu Color: 1
Material Pairs: [[3, 0], [2, 3], [0, 2], [1, 2]]
Material Zones: [2, 1, 3, 2]
Logistic Plan: [12, 13, 12, 13, 10, 11, 13, 10, 13, 12, 11, 10, 11, 13, 10, 11, 12, 11, 12, 10, 12]
Logistic Centers: [[0, 3, 1, 1], [1, 0, 2, 2], [1, 2, 0, 2], [3, 0, 2, 0]]

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

12
docs/source/lib/index.rst Normal file
View file

@ -0,0 +1,12 @@
compLib
#######
.. toctree::
:maxdepth: 5
classes/Motor
classes/Encoder
classes/IRSensor
classes/Seeding
classes/DoubleElimination
classes/Opencv

View file

@ -0,0 +1,64 @@
.. _other_bardware:
Hardware
########
Sensorarray
***********
|SensorarrayImage|
.. |SensorarrayImage| image:: images/Sensorarray.png
Specs V4
--------
| **Processor:** `STM32G030F6P6 <https://mou.sr/3UxW49B>`_ - 32-bit ARM Cortex M0 CPU @ 64 MHz
| **I/O:** 1x I2C, 1x SWD
| **Sensors:** 5x `QRE1113GR <https://mou.sr/3TWGYdI>`_
Specs V2
--------
| **Processor:** `ATMEGA328P-AU <https://mou.sr/3FxhPC5>`_ - 8-bit CPU @ 16 MHz
| **I/O:** 1x I2C, 1x UART, 1x ISP
| **Sensors:** 5x `QRE1113GR <https://mou.sr/3TWGYdI>`_
Details
-------
Das Sensorarray wird verwendet um Linienen vor dem Roboter zu erkennen. Es agiert als I2C Slave und muss dementsprechend aktiv gepollt werden.
Zusätzlich besteht die möglichkeit alle Emitter zu deaktiviern um einen eventuellen Messfehler durch Sonneneinstralung oder andere Störquellen zu erkennen.
Version 4 unterscheidet sich zu Version 2 im Mikroprozessor, da es zu Lieferengpässen des ATMEGA gekommen ist.
Zusätzlich wurde die möglichkeit alle Emitter einzeln an bzw. auszuschalten entfernt, da diese keinen signifikanten Mehrwert brachte.
Motorboard
**********
|MainboardImage|
.. |MainboardImage| image:: images/Mainboard.png
Specs
-----
**Motor-Treiber:** `LV8548MC-AH <https://mou.sr/3TXbFzu>`_
Details
-------
Das Motorboard kann an einen der 4 Ports am Roboter angesteckt werden und ermöglicht das Ansteuern von Motoren und auslesen von Encodern.
Mainboard
*********
Specs
-----
| **Processor:** `STM32L051C8T6TR <https://mou.sr/3fuaAQv>`_ - 32-bit ARM Cortex M0 @ 32MHz
| **I/O:** 4x I2C (3x Bus 1, 1x Bus 2), 1x 40 Pin GPIO Header, 2x SPI (Verbunden mit GPIO), 4x Motor-/Servo-connector, 1x SWD, 1x USB-C
Details
-------
Das Mainboard wird auf den GPIO-Header eines Raspberry Pi gesteckt und ermöglicht die Steuerung eines Roboters mittels 4 Motor- bzw. Servo-Ports. Der RaspberryPi kommuniziert dabei mittels SPI mit dem Mainboard und steuert die einzelnen Sensoren oder Module an.
Zusätzlich befinden sich auf der Unterseite des Mainboards Lötstellen, welche direkt mit der Stromversorgung der Motoren verbunden sind und geben so die möglichkeit Motoren mit mehr als 5V anzusteuern.

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

166
docs/source/other/usage.rst Normal file
View file

@ -0,0 +1,166 @@
.. _other_usage:
Beispiele
#########
Vorwärts und rückwärts fahren
*****************************
.. code-block:: python
import time
from compLib.Motor import *
def forward():
Motor.power(0, -30)
Motor.power(3, 30)
def backward():
Motor.power(0, 30)
Motor.power(3, -30)
def main():
print("hallo ich bin ein roboter beep buup")
forward()
time.sleep(1)
backward()
time.sleep(1)
if __name__ == '__main__':
main()
Eine Linie verfolgen
********************
.. code-block:: python
import time
from compLib.Motor import Motor
from compLib.Encoder import Encoder
from compLib.IRSensor import IRSensor
COLOR_BREAK = 850
DRIVE_SPEED = 35
IRSensor.enable()
def drive(left, right):
right *= -1
Motor.multiple_power((0, right), (3, left))
print(f"{left} {right}")
def follow():
while True:
sensors = IRSensor.read_all()
if sensors[0] > COLOR_BREAK:
# turn left
drive(-DRIVE_SPEED, DRIVE_SPEED)
elif sensors[4] > COLOR_BREAK:
# turn right
drive(DRIVE_SPEED, -DRIVE_SPEED)
else:
# straight
drive(DRIVE_SPEED, DRIVE_SPEED)
if sensors[0] > COLOR_BREAK and sensors[4] > COLOR_BREAK:
break
drive(0, 0)
time.sleep(1)
def main():
follow()
drive(DRIVE_SPEED, DRIVE_SPEED)
time.sleep(0.5)
follow()
drive(DRIVE_SPEED, DRIVE_SPEED)
time.sleep(0.5)
follow()
drive(DRIVE_SPEED, DRIVE_SPEED)
time.sleep(0.5)
follow()
if __name__ == "__main__":
main()
Funktionalität des Roboters überprüfen
**************************************
.. code-block:: python
import time
from compLib.Motor import Motor
from compLib.Encoder import Encoder
from compLib.IRSensor import IRSensor
def testIR():
print("Enabling Infrared Sensor")
IRSensor.enable()
time.sleep(1)
print("Writing sensor values...")
for i in range(0, 50):
print(IRSensor.read_all())
time.sleep(0.1)
print("Disabling Infrared Sensor")
IRSensor.disable()
def testEncoders():
Motor.multiple_pulse_width((0, 50), (3, -50))
print("Writing encoder positions...")
for i in range(0, 50):
print(Encoder.read_all_positions())
time.sleep(0.1)
time.sleep(2)
print("Writing encoder velocities...")
for i in range(0, 50):
print(Encoder.read_all_velocities())
time.sleep(0.1)
Motor.multiple_pulse_width((0, 0), (3, 0))
def testMotors():
print("Setting pulse_with")
Motor.multiple_pulse_width((0, 50), (3, -50))
time.sleep(3)
print("Setting power")
Motor.multiple_power((0, 50), (3, -50))
time.sleep(3)
print("Setting pulse_with")
Motor.multiple_speed((0, 5), (3, -5))
time.sleep(3)
for i in range(0, 100):
Motor.multiple_power((0, i), (3, -i))
time.sleep(0.1)
if __name__ == "__main__":
print("Make sure robot is turned on it's back!")
time.sleep(5)
print()
print("----------------- Testing Infrared Sensor -----------------")
testIR()
print()
print("----------------- Testing Encoder -----------------")
testEncoders()
print()
print("----------------- Testing Motors -----------------")
testMotors()

View file

@ -0,0 +1,59 @@
.. _software_installation:
Installationsanweisungen
########################
Diese Anleitung dient dazu die Software auf dem Roboter neu aufzusetzen.
**Im normalen Gebraucht sollte dies jedoch nicht notwendig sein.**
Betriebssystem aufsetzen
========================
Als Basis wird für den Roboter Raspberry Pi OS (64-bit) verwendet. Das 32-Bit Betriebssystem wird nicht unterstützt, da die Software-Komponenten nur für aarch64 bzw. arm64/v8 kompiliert werden.
Genauere Informationen sind `hier <https://www.raspberrypi.com/software/operating-systems/>`_ zu finden.
Bearbeiten der boot-Partition
=============================
1. ``cmdline.txt``
::
console=tty1 root=PARTUUID=21e60f8c-02 rootfstype=ext4 fsck.repair=yes rootwait quiet init=/usr/lib/raspi-config/init_resize.sh
Stellen Sie sicher, dass die folgenden Einstellungen in der ``config.txt`` korrekt gesetzt sind
2. ``config.txt``
::
# SPI
dtparam=spi=on
dtoverlay=spi1-3cs
# Run in 64-bit mode
arm_64bit=1
[all]
[pi4]
# Run as fast as firmware / board allows
arm_boost=1
[all]
start_x=1
gpu_mem=128
dtoverlay=pi3-disable-bt
enable_uart=1
3. Erstellen der leeren Datei ``ssh``, damit ssh beim nächsten Start aktiviert wird
4. Hinzufügen der ``userconf.txt``
::
compair:$6$eh2pkHm18RgYtwiG$PoeabVCH8llbyIio66OefPGXZ2r2BRI2hPHIdkNTBjmiR0lGXsozGyLx0uViOx3bi998syXjSDXkwt0t3x8Bq.
5. Wlan Verbindung einrichten

View file

@ -1,25 +0,0 @@
#!/usr/bin/env bash
# DO NOT RUN UNLESS YOUR REALLY KNOW WHAT YOU ARE DOING
DEBEMAIL="joel.klimont@gmail.com"
DEBFULLNAME="Joel Klimont"
export DEBEMAIL DEBFULLNAME
python3 setup.py sdist
cd dist || exit
tar -xzmf *.tar.gz
cd complib-0.0.1 || exit
git init
git remote add origin https://github.com/F-WuTS/complib-DEB.git
git fetch origin
git checkout -b master --track origin/master
gbp import-orig -u 0.0.1 --pristine-tar --sign-tags --no-interactive ../complib-0.0.1.tar.gz
mv setup.py setup.py.old
echo "#!/usr/bin/env python3" >> setup.py
cat setup.py.old >> setup.py
debmake -b":python3"
mv setup.py.old setup.py
git add debian/
git commit -m "init"
gbp buildpackage --git-pristine-tar --git-sign-tags --git-keyid=97B61E4D515353C0A498D2AB22680B5AAC0C4FCE
git push --set-upstream origin master

34
motorLinearityTest.py Normal file
View file

@ -0,0 +1,34 @@
from compLib.Motor import Motor
from compLib.Encoder import Encoder
from compLib.IRSensor import IRSensor
from compLib.CompLibClient import CompLibClient
import time
import math
CompLibClient.use_unix_socket()
sign = 1
for pwr in (18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 35, 40, 45, 50, 60, 70, 80 , 90, 100):
# Motor.multiple_pulse_width((0, pwr * sign), (3, -pwr * sign))
Motor.multiple_power((0, pwr * sign), (3, -pwr * sign))
start_time = time.time()
while time.time() - start_time < 3:
Encoder.read_all_velocities()
time.sleep(0.01)
avg = 0.0
for i in range(0, 20):
vels = Encoder.read_all_velocities()
avg += ((abs(vels[0]) + abs(vels[3])) / 2.0)
time.sleep(0.01)
avg = avg / 10
vel = str(avg).replace(".", ",")
print(f"{pwr} {vel}")
sign *= -1

45
postinstall.sh Executable file
View file

@ -0,0 +1,45 @@
#!/usr/bin/env bash
if [ "$(uname -m)" = "x86_64" ]; then
echo "Not running on RPi - Skipping postinstall"
exit 0
fi
grep -qxF "apt update" /etc/rc.local
if [ $? -ne 0 ]; then
echo "adding apt update to rc.local"
sed -i "2s/^/apt update\n/" /etc/rc.local
fi
pip3 install requests flask
#echo "Setting up nginx rtmp server"
#sudo /etc/init.d/nginx start
sudo raspi-config nonint do_legacy 0 || echo "(WARNING) raspi-config not found, cannot enable legacy camera support"
{
echo 'load_module "modules/ngx_rtmp_module.so";'
echo 'worker_processes auto;'
echo 'rtmp_auto_push on;'
echo 'events {}'
echo 'rtmp {'
echo ' server {'
echo ' listen 1935;'
echo ' listen [::]:1935 ipv6only=on;'
echo ' application live {'
echo ' live on;'
echo ' record off;'
echo ' }'
echo ' }'
echo '}'
} >| /etc/nginx/nginx.conf
echo "Stopping nginx rtmp server as its not required anymore"
sudo /etc/init.d/nginx stop
base64 -d << UPD
CiBfX19fX18gICAgIF9fX19fXyAgICAgX18gICAgX18gICAgIF9fX19fXyAgIF9fICAgICAgICAgX18gICAgIF9fX19fXyAgICAgICAgICAgICAgICAgIAovXCAgX19fXCAgIC9cICBfXyBcICAgL1wgIi0uLyAgXCAgIC9cICA9PSBcIC9cIFwgICAgICAgL1wgXCAgIC9cICA9PSBcICAgICAgICAgICAgICAgICAKXCBcIFxfX19fICBcIFwgXC9cIFwgIFwgXCBcLS4vXCBcICBcIFwgIF8tLyBcIFwgXF9fX18gIFwgXCBcICBcIFwgIF9fPCAgICAgICAgICAgICAgICAgCiBcIFxfX19fX1wgIFwgXF9fX19fXCAgXCBcX1wgXCBcX1wgIFwgXF9cICAgIFwgXF9fX19fXCAgXCBcX1wgIFwgXF9fX19fXCAgICAgICAgICAgICAgIAogIFwvX19fX18vICAgXC9fX19fXy8gICBcL18vICBcL18vICAgXC9fLyAgICAgXC9fX19fXy8gICBcL18vICAgXC9fX19fXy8gICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiBfXyAgX18gICAgIF9fX19fXyAgICAgIF9fX19fXyAgIF9fX19fXyAgICAgICAgX19fX18gICAgIF9fX19fXyAgICAgX19fX19fICAgX19fX19fICAgIAovXCBcL1wgXCAgIC9cICA9PSBcICAgIC9cX18gIF9cIC9cICBfXyBcICAgICAgL1wgIF9fLS4gIC9cICBfXyBcICAgL1xfXyAgX1wgL1wgIF9fX1wgICAKXCBcIFxfXCBcICBcIFwgIF8tLyAgICBcL18vXCBcLyBcIFwgXC9cIFwgICAgIFwgXCBcL1wgXCBcIFwgIF9fIFwgIFwvXy9cIFwvIFwgXCAgX19cICAgCiBcIFxfX19fX1wgIFwgXF9cICAgICAgICAgXCBcX1wgIFwgXF9fX19fXCAgICAgXCBcX19fXy0gIFwgXF9cIFxfXCAgICBcIFxfXCAgXCBcX19fX19cIAogIFwvX19fX18vICAgXC9fLyAgICAgICAgICBcL18vICAgXC9fX19fXy8gICAgICBcL19fX18vICAgXC9fL1wvXy8gICAgIFwvXy8gICBcL19fX19fLyA=
UPD
echo ""
echo ""

View file

@ -1,38 +1,24 @@
#!/usr/bin/env python3
#!/usr/bin/python3
import setuptools
import os
base_dir = os.path.dirname(__file__)
readme_path = os.path.join(base_dir, "README.md")
if os.path.exists(readme_path):
with open(readme_path) as stream:
long_description = stream.read()
else:
long_description = ""
print("Using version: {str(os.environ['VERSION'])}")
setuptools.setup(
name="complib",
version="0.0.1",
version=str(os.environ.get('VERSION', "")),
author="F-WuTs",
author_email="--",
author_email="joel.klimont@comp-air.at",
description="",
long_description=long_description,
long_description_content_type="text/markdown",
summary="Robot client library for the compAIR competition",
platforms=["any"],
url="https://github.com/F-WuTS/compLIB",
packages=setuptools.find_packages(),
include_package_data=True,
classifiers=[
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.9",
"License :: Other/Proprietary License",
"Operating System :: Unix"
],
license="proprietary",
install_requires=[
"smbus",
"requests"
],
setup_requires=[
"smbus",
"requests"
]
license="proprietary"
)

23
sphinx_to_github.sh Executable file
View file

@ -0,0 +1,23 @@
export BUILDING_DOCS=true
cd docs || exit
rm -rf build
rm -rf gh-pages
make html
git clone git@github.com:F-WuTS/compLIB.git gh-pages
cd gh-pages || exit
git checkout gh-pages
cp -r ../build/html/* .
git add .
git commit -a -m "Update documentation"
git push origin gh-pages
cd ..
rm -rf gh-pages
cd ..
export BUILDING_DOCS=false

150
test.py Normal file
View file

@ -0,0 +1,150 @@
import json
import unittest
import requests
import compLib.Seeding as Seeding
import compLib.Api as SeedingApi
import compLib.DoubleElimination as De
class SeedingTest(unittest.TestCase):
def test_basic_seed(self):
gamestate = Seeding.Gamestate(0)
self.assertEqual(gamestate.seed, 0)
self.assertEqual(gamestate.heu_color, 1)
self.assertEqual(gamestate.get_heuballen(), 1)
self.assertEqual(gamestate.get_logistic_plan(),
[12, 13, 10, 13, 12, 10, 11, 10, 12, 11, 12, 13, 10, 12, 10, 11, 13, 11, 13, 11, 12])
self.assertEqual(gamestate.get_material_deliveries(), [[3, 1], [0, 3], [3, 1], [3, 1]])
def util_get_info():
res = requests.get(SeedingApi.CONF_URL + "getInfo")
return json.loads(res.text)
def util_set_seeding():
res = requests.get(SeedingApi.CONF_URL + "setToSeeding")
return res.status_code == 200
def util_set_de():
res = requests.get(SeedingApi.CONF_URL + "setToDoubleElim")
return res.status_code == 200
def util_start_match():
res = requests.get(SeedingApi.CONF_URL + "startMatch")
return res.status_code == 200
def util_reset_state():
res = requests.get(SeedingApi.CONF_URL + "resetState")
return res.status_code == 200
def util_set_seed(seed):
res = requests.get(SeedingApi.CONF_URL + "resetState", params={"seed": seed})
return res.status_code == 200
class SeedingApiTest(unittest.TestCase):
def test_api_seeding_extensive(self):
self.assertTrue(util_set_seeding())
self.assertTrue(util_get_info()["is_seeding"])
for seed in range(0, 256):
print(f"Testing seed: {seed}")
gamestate = Seeding.Gamestate(seed)
self.assertTrue(util_set_seed(seed))
seeding_api = SeedingApi.Seeding()
self.assertEqual(seeding_api.get_heuballen(), gamestate.get_heuballen())
self.assertEqual(seeding_api.get_logistic_plan(), gamestate.get_logistic_plan())
self.assertEqual(seeding_api.get_material_deliveries(), gamestate.get_material_deliveries())
def test_gamestate(self):
seed = 42
gamestate = Seeding.Gamestate(seed)
print(gamestate)
print(gamestate.get_heuballen())
heu_color = gamestate.get_heuballen()
if heu_color == 1:
print("Heuballen liegen auf den gelben Linien")
# TODO: code um die über die gelben Linien zu fahren
elif heu_color == 2:
print("Heuballen liegen auf den blauen Linien")
# TODO: code um die über die blauen Linien zu fahren
materials = gamestate.get_material_deliveries()
print(materials)
for material_pair in materials:
print(f"Der Roboter sollte jetzt die beiden Materialien {material_pair} holen")
for material in material_pair:
if material == 0:
print(f"Der Roboter sollte jetzt Holz aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
elif material == 1:
print(f"Der Roboter sollte jetzt Stahl aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
elif material == 2:
print(f"Der Roboter sollte jetzt Beton aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
elif material == 3:
print(f"Der Roboter sollte jetzt Ziegelsteine aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
print("Der Roboter sollte jetzt die beiden Materialien zur Baustelle fahren")
# TODO: code um zur Baustelle zu fahren
logistic_plan = gamestate.get_logistic_plan()
print(logistic_plan)
for zone in logistic_plan:
if zone == 10:
print(f"Roboter sollte jetzt zur grünen Zone fahren: {zone}")
# TODO: code um in die grüne Zone zu fahren
elif zone == 11:
print(f"Roboter sollte jetzt zur roten Zone fahren: {zone}")
# TODO: code um in die rote Zone zu fahren
elif zone == 12:
print(f"Roboter sollte jetzt zur blauen Zone fahren: {zone}")
# TODO: code um in die blaue Zone zu fahren
elif zone == 13:
print(f"Roboter sollte jetzt zur gelben Zone fahren: {zone}")
# TODO: code um in die gelbe Zone zu fahren
class DeApiTest(unittest.TestCase):
def test_api_de(self):
self.assertTrue(util_set_de())
self.assertTrue(util_reset_state())
self.assertFalse(util_get_info()["is_seeding"])
de = De.DoubleElim()
self.assertEqual(de.get_pos(), (De.Position(0, 0, -1), 503))
self.assertEqual(de.get_opponent(), (De.Position(0, 0, -1), 503))
self.assertEqual(de.get_goal(), (De.Position(0, 0, -1), 503))
self.assertEqual(de.get_items(), ([], 503))
self.assertEqual(de.get_scores(), ({"self": 0, "opponent": 0}, 503))
self.assertTrue(util_start_match())
self.assertLessEqual(util_get_info()["timeleft"], 120)
self.assertEqual(de.get_pos()[1], 200)
self.assertEqual(de.get_opponent()[1], 200)
self.assertEqual(de.get_goal()[1], 200)
self.assertEqual(de.get_items()[1], 200)
self.assertEqual(de.get_scores()[1], 200)
self.assertTrue(0 <= de.get_pos()[0].x <= 250)
self.assertTrue(0 <= de.get_pos()[0].y <= 250)
self.assertTrue(0 <= de.get_pos()[0].degrees <= 360)
self.assertEqual(de.get_items(), ([], 200))
self.assertTrue(util_reset_state())
if __name__ == '__main__':
unittest.main()