# Zenmav – Drone Control Library

[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

Zenmav is a lightweight Python wrapper that lets you write quick and simple scripts for ArduPilot-controlled drones in either SITL or real hardware.
Developed by **Zenith Polytechnique Montréal**.

## Key Features

- **Simplified Pymavlink Commands with Feedback** – one-line TCP/UDP connection with heartbeat check and easy mode change
- **Precise Navigation and commands**
  
  - Global GPS waypoints with user-defined accuracy
  - Local NED waypoints relative to home
  - Real-time body-frame speed control
  - RC override commands
- **Autonomous Area Scans**
  
  - Rectilinear/lawn-mower pattern
  - Spiral pattern
  - **Mavlink commands and utilities**
  - Mode change
  - Arming / disarming
  - Takeoff
  - RTL
  - Reading and setting parameters
  - **Mavlink connection relay system**
  - UDP, TCP, and UDPCI mavlink relays for GCS applications and other scripts
- **Live Telemetry** – local position, global GPS, heading, RC channels, battery voltage and current
- **CSV Logging Utilities**

## Safety Disclaimer

**Always test new scripts in SITL first.**
When flying a real aircraft you are solely responsible for airworthiness, regulatory compliance and safe operation.
Operate in a clear area, keep visual line-of-sight and have a manual RC transmitter ready to take over. It is highly recommended **NOT** to use the arming command inside a script for a real drone unless thoroughly tested.

---

## Installation

```bash
pip install zenmav
```

SITL users only need Ardupilot sitl running on port 5762 (default shown below). It it possible to use other AP's simulation like gazebo too. Make sure you enter the right ip when initializing Zenmav()

---

## Quick Start

Below are three concise, copy-paste-ready examples that showcase different parts of Zenmav’s API.
Each script follows the same basic pattern—connect, arm, fly, land—while using a different feature set.

---

Example 1 — Fly to a local waypoint and come back
--------------------------------------------------

```python
# example_local_target.py

import time
from zenmav.core import Zenmav

def main() -> None:
    drone = Zenmav()                 # connect (default SITL TCP link)
    drone.arm()
    drone.set_mode("GUIDED")
    drone.takeoff(altitude=15)       # climb to 15 m AGL

    # Fly 30 m North, 20 m East, -10 m down (NED, means 10 m in alt)
    print("Navigating to local waypoint …")
    drone.local_target([30, 20, -10])

    # Hold position 5 s
    time.sleep(5)

    # Return-to-Launch (waits for landing & disarm)
    drone.RTL()

if __name__ == "__main__":
    main()
```

---

Example 2 — Quick lawnmower of a 50 m radius area
--------------------------------------------------

```python
# example_spiral_scan.py
# Demonstrates rectilinear_scan()

from zenmav.core import Zenmav

def main() -> None:
    drone = Zenmav(gps_thresh=3)     # enables specific waypoint-reach detection in metres, default is current value of controller + 0.5 m
    drone.arm()
    drone.set_mode("GUIDED")
    drone.takeoff(altitude=25)       # climb to 25 m AGL

    print("Starting spiral scan …")
    drone.rectilinear_scan(
        largeur_detection=8,         # 8 m sensor footprint
        altitude=25,                 # keep current altitude
        rayon_scan=50,               # cover a 50 m radius
        safety_margin=10             # add 10 m buffer
    )

    drone.RTL()

if __name__ == "__main__":
    main()
```

---

Example 3 — Log a GPS point to CSV and adjust cruise speed
-----------------------------------------------------------

```python
# example_csv_and_params.py
# Shows get_global_pos(), insert_coordinates_to_csv(), set_param()   (zenmav 0.0.5)

import csv
from pathlib import Path
from zenmav.core import Zenmav
import time
CSV_FILE = Path("waypoints.csv")

def main() -> None:
    drone = Zenmav(ip="/dev/ttyACM0") # example connection to pixhawk via USB
    drone.arm()
    drone.set_mode("GUIDED")
    drone.takeoff(altitude=10)

    # Grab current location and write it to CSV
    lat, lon, rel_alt = drone.get_global_pos()
    desc = "Take-off point"
    drone.insert_coordinates_to_csv(CSV_FILE, (lat, lon), desc)
    print(f"Saved home waypoint to {CSV_FILE}")

    # Slow the aircraft to 3 m/s (ArduPilot expects cm/s)
    drone.set_param("WPNAV_SPEED", 300)
    print("WPNAV_SPEED set to 3 m/s")

    # Fly forward at 3 m/s for ~3 s using body-frame velocity
    for _ in range(30):
        drone.speed_target([3, 0, 0])     # 3 m/s forward, level flight
	time.sleep(0.1)
    print("Short cruise complete")

    drone.RTL()

    # Optional: show CSV content
    print("\nCSV content:")
    with CSV_FILE.open() as fp:
        print(fp.read())

if __name__ == "__main__":
    main()
```

---

## Zenmav Docs

Available in docs/zenmav.md

---

## Troubleshooting

| Symptom                       | Fix                                    |
| ----------------------------- | -------------------------------------- |
| `No heartbeat in 5 seconds` | Check connection string / firewall     |
| `PermissionError: ttyACM0`  | `sudo usermod -aG dialout $USER`     |
| Waypoint never “reached”    | Provide `gps_thresh` at construction |
| Takeoff not working           | Make sure to be in Guided mode         |

---

## Contributing

Issues and pull requests are welcome!
Please fork the repository, create a feature branch and open an MR when ready.

---

## Maintainer

To push new version, update pyproject.toml

``python3 -m build``

Upload with

``twine upload dist/*``

Enter API Token (Currently only accessible by maintainer Colin Rousseau)

