Ce référentiel contient du code, de la documentation et d'autres éléments liés au projet de voiture jouet jaune que j'ai réalisé.
J'ai également créé l'application mobile Flutter pour contrôler la petite voiture, voir le référentiel YellowToyCarApp.
Le matériel se compose de :
Le logiciel se compose de :
/
ou /index
ou /index.html
→ Site Web présenté à l'utilisateur pour contrôler la voiture.
/status
→ État de base, y compris l'heure, l'état des lumières et des moteurs et d'autres données de diagnostic.
{
"uptime" : 123456 , // Microseconds passed from device boot.
"time" : "2023-01-12T23:49:03.348+0100" , // Device time, synced using SNTP.
"rssi" : - 67 , // Signal strength of AP the device is connected to, or 0 if not connected.
/* With `?details=1` querystring parameter, extended response is provided. */
"stations" : [ "a1:b2:c3:d4:e5:f6" ] , // list of stations currently connected to our AP
}
/config
→ Point de terminaison pour les demandes de définition de configuration (API JSON GET/POST)
{
/* Control & config for motors and lights */
"control" : {
/* Other */
"timeout" : 2000 , // Time in milliseconds counted from last control request/packet, after which movement should stop for safety reason
/* Input values */
"mainLight" : 1 ,
"otherLight" : 1 ,
"left" : 12.3 , // The motors duty cycle are floats as percents,
"right" : 12.3 , // i.e. 12.3 means 12.3% duty cycle.
/* Calibration */
"calibrate" : {
"left" : 0.95 , // Inputs will be multiplied by calibration values before outputting PWM signal.
"right" : 1.05 ,
"frequency" : 100 , // Frequency to be used by PWMs
}
} ,
/* Networking related. Some things are not implemented, including: DNS and DHCP leases */
"network" : {
"mode" : "ap" , // for Access Point or "sta" for station mode, or "nat" (to make it work like router)
"fallback" : 10000 , // duration after should fallback to hosting AP if cannot connect as station
"dns1" : "1.1.1.1" ,
"dns2" : "1.0.0.1" ,
"sta" : {
"ssid" : "YellowToyCar" ,
"psk" : "AAaa11!!" ,
"static" : 0 , // 1 if static IP is to be used in STA mode
"ip" : "192.168.4.1" ,
"mask" : 24 , // as number or IP
"gateway" : "192.168.4.1"
} ,
"ap" : {
"ssid" : "YellowToyCar" ,
"psk" : "AAaa11!!" ,
"channel" : 0 , // channel to use for AP, 0 for automatic
"hidden" : 0 ,
"ip" : "192.168.4.1" ,
"mask" : 24 , // as number or IP
"gateway" : "192.168.4.1" ,
"dhcp" : {
"enabled" : 1 ,
"lease" : [ "192.168.4.1" , "192.168.4.20" ] ,
}
} ,
"sntp" : {
"pool" : "pl.pool.ntp.org" ,
"tz" : "CET-1CEST,M3.5.0,M10.5.0/3" ,
"interval" : 3600000
}
} ,
/* Camera settings. See this project or `esp32_camera` library sources for details. */
"camera" : {
"framesize" : 13 ,
"pixformat" : 4 ,
"quality" : 12 ,
"bpc" : 0 ,
"wpc" : 1 ,
"hmirror" : 0 ,
"vflip" : 0 ,
"contrast" : 0 ,
"brightness" : 0 ,
"sharpness" : 0 ,
"denoise" : 0 ,
"gain_ceiling" : 0 ,
"agc" : 1 ,
"agc_gain" : 0 ,
"aec" : 1 ,
"aec2" : 0 ,
"ae_level" : 0 ,
"aec_value" : 168 ,
"awb" : 1 ,
"awb_gain" : 1 ,
"wb_mode" : 0 ,
"dcw" : 1 ,
"raw_gma" : 1 ,
"lenc" : 1 ,
"special" : 0
}
}
Renvoie le JSON de la configuration actuelle, si rien ne change.
192.168.4.1
pour le moment, car les paramètres DHCP sont codés en dur sur certaines valeurs par défaut. /capture
→ Capture d'image depuis la caméra de la voiture.
:81/stream
→ Flux d'images continu depuis la caméra de la voiture en utilisant MJPEG qui exploite un type de contenu spécial : multipart/x-mixed-replace
qui informe le client de remplacer l'image si nécessaire. Un serveur HTTP séparé est utilisé (d'où le port non standard 81), car il s'agit du moyen le plus simple d'envoyer en continu des parties (trames suivantes) dans cette seule requête sans fin.
L'application attend les paquets UDP sur le port 83.
Octuor | 0 | 1 | 2 | 3 | |
---|---|---|---|---|---|
Octuor | Morceaux | 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 | 16 17 18 19 20 21 22 23 | 24 25 26 27 28 29 30 31 |
0 | 0 | (UDP) Port source | (UDP) Port de destination | ||
4 | 32 | (UDP) Longueur | (UDP) Somme de contrôle | ||
8 | 64 | Type de paquet (toujours 1) | Drapeaux (voir tableau ci-dessous) | Service moteur gauche | Service moteur droit |
Peu | Masque | Description |
---|---|---|
0 | 0b00000001 | Lumière principale (LED blanche brillante externe) |
1 | 0b00000010 | Autre lumière (petite LED rouge interne) |
2 | 0b00000100 | Réservé |
3 | 0b00001000 | Réservé |
4 | 0b00010000 | Réservé |
5 | 0b00100000 | Réservé |
6 | 0b01000000 | Sens du moteur gauche |
7 | 0b10000000 | Sens du moteur droit |
0
) signifie avant, le bit défini ( 1
) signifie arrière. Octuor | 0 | 1 | 2 | 3 | |
---|---|---|---|---|---|
Octuor | Morceaux | 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 | 16 17 18 19 20 21 22 23 | 24 25 26 27 28 29 30 31 |
0 | 0 | (UDP) Port source | (UDP) Port de destination | ||
4 | 32 | (UDP) Longueur | (UDP) Somme de contrôle | ||
8 | 64 | Type de paquet : 2 | Drapeaux (voir ci-dessous) | Temps (en millisecondes) pour lisser le mélange vers les valeurs cibles du moteur | |
12 | 96 | Service du moteur gauche, pourcentage sous forme de flotteur (c'est-à-dire 63.8f équivaut à un cycle de service de 63,3 %) | |||
16 | 128 | Service du moteur droit, pourcentage sous forme de flotteur (c'est-à-dire 63.8f équivaut à un cycle de service de 63,3 %) |
Certains scripts ont été développés pour faciliter le développement et l'utilisation.
$ python . s cripts c onfig.py --help
usage: config.py [-h] [--status] [--status-only] [--config-file PATH] [--wifi-mode {ap,sta,apsta,nat,null}] [--ip IP] [--read-only] [--restart [RESTART]]
This script allows to send & retrieve config from the car.
optional arguments:
-h, --help show this help message and exit
--status Request status before sending/requesting config.
--status-only Only request status.
--config-file PATH JSON file to be send as config.
--wifi-mode {ap,sta,apsta,nat,null}
Overwrite WiFi mode from config.
--ip IP, --address IP
IP of the device. Defaults to the one used for AP mode from new config or 192.168.4.1.
--read-only If set, only reads the request (GET request instead POST).
--restart [TIMEOUT] Requests for restart after updating config/retrieving the config.
$ python . s cripts c ontrol.py --help
usage: control.py [-h] [--ip IP] [--port PORT] [--interval INTERVAL] [--dry-run] [--show-packets] [--short-packet-type] [--no-blink] [--max-speed VALUE] [--min-speed VALUE] [--acceleration VALUE]
This script allows to control the car by continuously reading keyboard inputs and sending packets.
optional arguments:
-h, --help show this help message and exit
--ip IP, --address IP
IP of the device. Default: 192.168.4.1
--port PORT Port of UDP control server. Default: 83
--interval INTERVAL Interval between control packets in milliseconds. Default: 100
--dry-run Performs dry-run for testing.
--show-packets Show sent packets (like in dry run).
--short-packet-type Uses short packet type instead long.
--no-blink Prevents default behaviour of constant status led blinking.
Driving model:
--max-speed VALUE Initial maximal speed. From 0.0 for still to 1.0 for full.
--min-speed VALUE Minimal speed to drive motor. Used to avoid motor noises and damage.
--acceleration VALUE Initial acceleration per second.
Note: The 'keyboard' library were used (requires sudo under Linux), and it hooks work also out of focus, which is benefit and issue at the same time, so please care.
Controls:
WASD (or arrows) keys to move; QE to rotate;
F to toggle main light; R to toggle the other light;
Space to stop (immediately, uses both UDP and HTTP);
V to toggle between vectorized (smoothed) and raw mode;
+/- to modify acceleration; [/] to modify max speed;
Shift to temporary uncap speed; ESC to exit.
Nom convivial | Nom | Affinité | Priorité | Fichier source | Description |
---|---|---|---|---|---|
Tâches IPC | ipcx * | Tous* | 0 | (interne) | Les tâches IPC sont utilisées pour implémenter la fonctionnalité d'appel inter-processeur. |
Principal | main | CPU0 | 1 | main.cpp | Initialise tout, démarre d'autres tâches, puis exécute la logique d'arrière-plan. |
Flux de caméra | httpd | CPU0 | 5 | camera.cpp | |
LwIP | ? | ||||
Wi-Fi | CPU0 | ||||
Événements | ? | ||||
Tâches inactives | ipcx * | Tous* | 24 | (interne) | Tâches inactives créées pour (et épinglées) sur chaque processeur. |
* - Certaines tâches fonctionnent sur plusieurs processeurs, en tant que tâches distinctes.
struct
. Voir la discussion ici. Comme solution, j'ai découvert qu'il était plus simple d'utiliser strncpy
, qui est intégré/optimisé._binary_src_
lors de l'accès aux étiquettes de début/fin des blocs de données intégrés (comme dans la macro GENERATE_HTTPD_HANDLER_FOR_EMBEDDED_FILE
), ce n'est pas vrai. La documentation semble obsolète ou invalide dans certains domaines, du moins pour esp-idf
. Cependant, j'ai trouvé une solution : utilisez à la fois board_build.embed_files
dans platformio.ini
et également EMBED_FILES
dans CMakeLists.txt
. Dans le code, utilisez _binary_
, sans la partie src_
.snake_case
mélangé à camelCase
car nous utilisons les bibliothèques C d'ESP-IDF et certaines parties les utilisent beaucoup. C'est encore plus laid de monter un seul chameau au milieu des serpents.ESP_LOGV
et ESP_LOGD
pour un seul fichier, j'ai donc redéfini ces macros en ESP_LOGI
comme solution de contournement.esp32-camera
utilisée par le projet présente des problèmes étranges, en voici quelques-uns :camera.py
, qui inclut la tâche de l'envoyer via WiFi :string_view
s, comme dans le code associé à config/JSON. J'ai récemment eu un problème avec le fait que strlen
n'était pas sûr...vTaskList
/ uxTaskGetSystemState
Kconfig
pour y conserver les fonctionnalités facultatives, y compris certains débogages. Voir également https://esp32tutorials.com/esp32-static-fixed-ip-address-esp-idf/esp32-camera
fb_size
lors de l'utilisation de JPEG pour permettre au plus petit 96x96 de fonctionner. Avoir un minimum de 2048 semble fonctionner, en utiliser davantage pour faire bonne mesure semble conseillé. (problème sur github).xclk_freq_hz = 10'000'000,
pour camera_config_t
? 10 MHz pourraient être meilleurs que 20 MHz, voir espressif/esp32-camera#15COM8_AGC_EN
dans la caméra n'enregistre-t-il pas les définitions de 1 ?constexpr
rapide et C++ vers la fonction IP 4