├── .ansible └── .lock ├── .gitattributes ├── .gitbook.yaml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── story-template.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docker-frigate ├── config │ └── config.yml.j2 ├── docker-compose-frigate.yaml.j2 └── frigate.env.j2 ├── docs ├── .gitbook │ └── assets │ │ ├── Screenshot_20250306_110924.png │ │ ├── Screenshot_20250307_143756.png │ │ ├── build-it.png │ │ ├── icon-diy-350x200px-black-bg_grey.png │ │ ├── icon-shop-350x200px-black-bg_grey.png │ │ ├── store.svg │ │ ├── tm-display-pic01.jpg │ │ ├── tm-roadway-graphic-1920x1080-color-bg_black (1).png │ │ ├── tm-roadway-graphic-1920x1080-color-bg_black.png │ │ ├── tm-roadway-graphic-1920x1080-color-bg_white.png │ │ ├── tm-roadway-graphic-transparent-grey.png │ │ └── tools.svg ├── README.md ├── SUMMARY.md ├── build-your-own-device-diy │ ├── README.md │ ├── assembly-instructions.md │ └── software-installation.md ├── configuration │ ├── frigate-config.md │ ├── node-red-config.md │ └── overview.md ├── deployment-and-mounting-guide.md ├── development │ ├── contributing.md │ └── dev-environment.md ├── getting-started.md ├── help-and-faq │ ├── frequently-asked-questions.md │ └── where-can-i-get-support.md ├── recommended-hardware.md ├── sensor-payloads │ ├── air-quality-aq-payload.md │ ├── events-payload.md │ └── radar-payload.md └── setup-guide.md ├── node-red-tm ├── Dockerfile.j2 ├── config │ └── config.yml.j2 ├── data │ ├── README.md │ ├── flows.json │ ├── flows_cred.json │ ├── package.json.j2 │ └── settings.js ├── docker-compose-node-red-tm.yaml.j2 ├── node-red-tm └── node-red-tm.env.j2 ├── script ├── ansible │ ├── localhost │ ├── roles │ │ └── tmsetup │ │ │ ├── README.md │ │ │ ├── defaults │ │ │ └── main.yml │ │ │ ├── files │ │ │ ├── 65-apex.rules │ │ │ ├── gasket-builder.Dockerfile │ │ │ ├── go2rtc.yaml │ │ │ ├── node-red-tm │ │ │ ├── utils │ │ │ ├── wlan_check.sh │ │ │ └── z-wifi-powersave-off.conf │ │ │ ├── handlers │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ ├── base_setup.yml │ │ │ ├── coraltpu_pci_setup.yml │ │ │ ├── coraltpu_setup.yml │ │ │ ├── docker_setup.yml │ │ │ ├── frigate_setup.yml │ │ │ ├── go2rtc_setup.yml │ │ │ ├── main.yml │ │ │ ├── node-red-tm_docker_setup.yml │ │ │ ├── set_vars.yml │ │ │ ├── start_services.yml │ │ │ └── wifi_setup.yml │ │ │ ├── templates │ │ │ ├── compose.yaml.j2 │ │ │ ├── frigate │ │ │ ├── node-red-tm │ │ │ └── services │ │ │ │ ├── go2rtc_server.service.j2 │ │ │ │ └── tm-docker.service.j2 │ │ │ └── vars │ │ │ ├── aarch64_vars.yml │ │ │ ├── main.yml │ │ │ └── x86_64_vars.yml │ └── setup.yml ├── requirements ├── tmsetup.sh └── update ├── static └── img │ ├── aq-dash1.png │ ├── dashboard-sample-01.png │ ├── event-car-speed-stats-daily.png │ ├── events-counts-daily.png │ ├── events-daily-10-day.png │ ├── events-hourly-24-hours.png │ ├── events-zone_radar-last5.png │ ├── gitflow-trans-dark.drawio.png │ └── tm-roadway-graphic.png └── utils ├── backup ├── network-wlan-ap ├── nm-connectivity-check.conf ├── pineboard-pcie-tpu ├── psu-battery ├── rpi5-nodered-telemetry.py ├── wlan_check.sh └── z-wifi-powersave-off.conf /.ansible/.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/.ansible/.lock -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text eol=lf 3 | 4 | # Denote all files that are truly binary and should not be modified. 5 | *.png binary 6 | *.jpg binary 7 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ## To Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | ## Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | ## Screenshots 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | ## Hardware/Software Environment 27 | Raspberry Pi (please complete the following information): 28 | - Pi version [e.g. RPi5/4GB] 29 | - OS: [e.g. iOS] 30 | - Software Version [e.g. 22] 31 | 32 | ## Additional context 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Problem 11 | **Is your feature request related to a problem? Please describe.** 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | ## Proposed Solution 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | ## Alternative Solutions 19 | **Describe alternatives you've considered** 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | ## Additional Info 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/story-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Story template 3 | about: Issues created as part of Milestones, Projects, or ready with all information 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Story 11 | 12 | As a [persona], I [want to...], [so that]. 13 | 14 | ## Definition of done 15 | 16 | Needs to have ABC and XYZ deployed in prod. 17 | 18 | ## Out-of-scope 19 | 20 | ##Sizing 21 | 22 | Complexity + Unknowns = Est. Size 23 | 24 | ## Tasks 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.backup 2 | .vscode 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | @glossyio. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing! We need volunteer contributors like you to help grow this project. 4 | 5 | You can find us at https://trafficmonitor.zulipchat.com/ for discussions, questions, and immediate community-based assistance. 6 | 7 | Important resources (many coming soon): 8 | - Use GitHub Issues to [report bugs](https://github.com/glossyio/traffic-monitor/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=) and make [feature requests](https://github.com/glossyio/traffic-monitor/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=) 9 | - Use [GitHub Discussions](https://github.com/glossyio/traffic-monitor/discussions/new/choose) to deep-dive and get support. 10 | - [Traffic Monitor Zulic Chat](https://trafficmonitor.zulipchat.com/) to connect with other contributors and chat for community support. 11 | - About us 12 | - Roadmap 13 | - Project management (Taiga) 14 | 15 | ## Testing 16 | 17 | We have not implemented a testing framework. Help us by suggesting or creating one! 18 | 19 | ## Submitting changes 20 | 21 | Please send a [GitHub Pull Request to this repo](https://github.com/glossyio/traffic-monitor/compare) with a clear list of what you have done (read more about [pull requests](http://help.github.com/pull-requests/)). Please follow our coding conventions (below) and make sure all of your commits are atomic (one feature per commit). 22 | 23 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: 24 | 25 | $ git commit -m "A brief summary of the commit 26 | > 27 | > A paragraph describing what changed and its impact." 28 | 29 | ## Coding conventions 30 | See our current code to help you get the hang of it: 31 | - Indent with 4 spaces 32 | - This is open source software. Consider the people who will read your code, and make it look nice for them. 33 | - We use Linux line endings (see [GitHub Docs - About line endings](https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings)) 34 | 35 | ## Branching strategy / git workflow 36 | We currently employ the `main` branch as default for PRs and maintain it as the development branch and create tagged released for general consumption. Remember when you perform a `git clone` it will pull down the `main` branch by default with all the development code. We recommend you only use tagged releases for non-development deployments. 37 | 38 | ![Git flow branching strategy diagram](static/img/gitflow-trans-dark.drawio.png) 39 | 40 | ## Git naming convention / branch naming 41 | Shall: 42 | - Include a short descriptive summary in imperative present tense 43 | - Use Hyphens for separating words 44 | 45 | Should: 46 | - Include the work type: 47 | - `feature` for new functionality 48 | - `refactor` for update to existing functionality 49 | - `bugfix` for corrections to existing functionality 50 | - `hotfix` for critical fix to main 51 | - Include corresponding ticket/story id (e.g. from Jira, GitHub issue, etc.) 52 | 53 | Suggested Format: 54 | `{work-type}/{short-summary}/{story-issue-ticket-id}` 55 | 56 | Example: 57 | ```git 58 | git checkout -b feature/oauth-migration/ATL-244 59 | ``` 60 | ## Thank you! 61 | Thank you for your interest and contributions! -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Traffic Monitor 2 | 3 | ![GitHub Release](https://img.shields.io/github/v/release/glossyio/traffic-monitor) 4 | ![GitHub License](https://img.shields.io/github/license/glossyio/traffic-monitor) 5 | Static Badge 6 | 7 | [TrafficMonitor.ai](https://www.trafficmonitor.ai/), the Traffic Monitor, is an open source smart city traffic monitoring software built with commodity hardware to capture holistic roadway usage. Utilizing edge machine learning object detection and Doppler radar, it counts pedestrians, bicycles, and cars and measures vehicle speeds. 8 | 9 | ![Traffic Monitor Roadway Graphic](static/img/tm-roadway-graphic.png) 10 | 11 | ## Highlights and capabilities 12 | - 🧮 Reporting roadway utilization - counting cars 🚗, bicycles 🚲, pedestrians 🚶, and more! 🐕🐈🏍️🚜 13 | - 🚨 Capturing roadway users' speeds - measuring speeds, capturing image and video of event. 14 | - 📷 Privacy-focused, local inferencing (no cloud required) only collects and sends data you specify. 15 | - 🐘 Permanent, long-term deployment on roadways to monitor roadway usage. 16 | - 🐜 Temporary, remote deployments utilizing the low-power footprint and batteries. 17 | - 💡 Extensible with any Raspberry Pi-compatible senosrs and components; e.g. Air Quality sensor for analog gas: RED, OX, NH3, noise, light, proximity, temperature, pressure, humidty and Particulate Matter (PM 5, 2.5, 1). 18 | 19 | ## Documentation 📚 20 | Visit our official documentation at [docs.trafficmonitor.ai](https://docs.trafficmonitor.ai/) for an introduction, recommended hardware, deployment and mounting guide, setup guide, configuration, data payloads, and more. 21 | 22 | ## Build your own device (DIY) 🧑‍🔬 23 | 24 | ### Getting Started 25 | 26 | See the [Getting Started](https://docs.trafficmonitor.ai/getting-started) docs for full walkthrough. 27 | 28 | 1. Assemble your device (see [hardware components](#hardware-components) ⚒️). 29 | 1. Install git if using OS Lite `sudo apt update && sudo apt install git` 30 | 2. Run `git clone https://github.com/glossyio/traffic-monitor` into your home folder (or any folder) 31 | 3. Run `bash traffic-monitor/script/tmsetup.sh` 32 | 4. Access the application at the following URLs (check container status with `systemctl status tm-docker` or `docker ps`): 33 | 1. `http://:1880/ui` is your primary device dashboard, use it to ensure it is capturing events (Node-Red dashboard) 34 | 2. `http://:5000` to view the Frigate interface and make any configuration changes specific to your deployment 35 | 5. Mount your device in a place it can capture the entire roadway in the mounting guide (coming soon). 36 | 6. [Configure your device](#configuration) 37 | 7. Start capturing roadway usage data! 38 | 39 | ### Hardware Components 🛠️ 40 | The Traffic Monitor is designed on the [Raspberry Pi 5](https://www.raspberrypi.com/products/raspberry-pi-5/) and a variety of commidity hardware to keep it accessible, low-cost, upgradable, and repairable. See [Recommended Hardware](https://docs.trafficmonitor.ai/build-your-own-device-diy/recommended-hardware) docs for more information. 41 | 42 | ## Configuration ✅ 43 | See [Setup Guide](https://docs.trafficmonitor.ai/setup-guide). 44 | 45 | ## User Interfaces (UI) / port numbers references 🖥️ 46 | - `http://:1880/ui` is the [Node-RED](https://nodered.org/) dashboard and your primary device dashboard, use it to ensure it is capturing events, see the latest events, and see summarized stats. 47 | - `http://:5000` to view the [Frigate](https://github.com/blakeblackshear/frigate) interface and make any configuration changes specific to your deployment 48 | - `http://:1984` shows the [go2rtc](https://github.com/AlexxIT/go2rtc) configured camera settings on the Raspberri Pi. Use this if your cameras are giving errors in Frigate. 49 | - `http://:1880` is the Node-RED flow editor used to develop the logic to connect events to speeds, create event records, capture radar readings, and more. This is primarily used by developers. The default admin login is admin/password. 50 | 51 | ## Troubleshooting ⚠️ 52 | 53 | No events are being captured? Review the [Setup Guide](https://docs.trafficmonitor.ai/setup-guide) to ensure the zones are properly set up and the camera is enabled and detecting: 54 | - In Frigate > Settings> Configuration Editor: 55 | - Enable camera(s) (`cameras` --> `enabled: true`) 56 | - Enable detection (`detect` --> `enabled: true`) 57 | - In Node-RED: 58 | - Enable camera(s) (`sensors` --> `cameras` --> `your_cam_name` --> `enabled: true`) 59 | 60 | If you are having detection issues, review the [Frigate Camera Setup](https://docs.frigate.video/frigate/camera_setup) documentation. 61 | 62 | ## Screenshots 63 | Device dashboard available at `http://:1880/ui`. 64 | 65 | Cumulative events by object for the day (resets daily). 66 | ![events-counts-daily](static/img/events-counts-daily.png) 67 | 68 | Last 5 events detected and confirmed by radar. 69 | ![events-zone_radar-last5](static/img/events-zone_radar-last5.png) 70 | 71 | Car object speed statistics for the day (resets daily). 72 | ![event-car-speed-stats-daily](static/img/event-car-speed-stats-daily.png) 73 | 74 | Hourly events count by object for the last 24-hours. 75 | ![events-hourly-24-hours](static/img/events-hourly-24-hours.png) 76 | 77 | Daily events count by object for the last 10-days. 78 | ![events-daily-10-day](static/img/events-daily-10-day.png) 79 | 80 | Dashboard sample, using the granular backend data 81 | ![dashboard-sample](static/img/dashboard-sample-01.png) 82 | 83 | Air Quality sample 84 | ![air-quality-sample](static/img/aq-dash1.png) 85 | -------------------------------------------------------------------------------- /docker-frigate/config/config.yml.j2: -------------------------------------------------------------------------------- 1 | # see https://docs.frigate.video/configuration/reference 2 | mqtt: 3 | enabled: true 4 | host: 172.18.0.1 5 | port: 1883 6 | {% raw %} 7 | user: '{FRIGATE_MQTT_USER}' 8 | password: '{FRIGATE_MQTT_PASSWORD}' 9 | {% endraw %} 10 | 11 | detectors: 12 | # coralpci: 13 | # type: edgetpu 14 | # device: pci 15 | # coralusb: 16 | # type: edgetpu 17 | # device: usb 18 | mycpu: 19 | type: cpu 20 | 21 | ffmpeg: 22 | hwaccel_args: preset-rpi-64-h264 23 | 24 | cameras: 25 | picam_h264: 26 | enabled: true 27 | ffmpeg: 28 | inputs: 29 | - path: rtsp://172.18.0.1:8554/picam_h264 30 | roles: 31 | - detect 32 | detect: 33 | enabled: false 34 | width: 2304 35 | height: 1296 36 | fps: 10 37 | zones: 38 | zone_capture: 39 | coordinates: 2304,1296,0,1296,0,493,998,268,2304,475 40 | zone_far: 41 | coordinates: 1248,556,926,497,231,657,477,769 42 | zone_near: 43 | coordinates: 1690,695,1355,563,429,754,825,982 44 | zone_radar: 45 | coordinates: 390,634,1039,464,1891,684,1087,1029 46 | 47 | objects: 48 | track: 49 | - bicycle 50 | - person 51 | - car 52 | - motorcycle 53 | - dog 54 | filters: 55 | bicycle: 56 | min_area: 0 57 | max_area: 24000000 58 | min_ratio: 0.2 59 | max_ratio: 10.0 60 | min_score: 0.25 61 | threshold: 0.42 62 | motorcycle: 63 | min_area: 0 64 | max_area: 24000000 65 | min_ratio: 0.2 66 | max_ratio: 10.0 67 | min_score: 0.5 68 | threshold: 0.8 69 | 70 | record: 71 | enabled: true 72 | retain: 73 | days: 0 74 | alerts: 75 | retain: 76 | days: 1 77 | detections: 78 | retain: 79 | days: 1 80 | snapshots: 81 | enabled: true 82 | clean_copy: true 83 | bounding_box: true 84 | crop: false 85 | retain: 86 | default: 1 87 | version: 0.15-1 88 | -------------------------------------------------------------------------------- /docker-frigate/docker-compose-frigate.yaml.j2: -------------------------------------------------------------------------------- 1 | services: 2 | frigate: 3 | container_name: frigate 4 | privileged: false 5 | restart: unless-stopped 6 | image: ghcr.io/blakeblackshear/frigate:{{ tmsetup_frigate_version }} 7 | shm_size: "256MB" 8 | devices: 9 | - /dev/bus/usb:/dev/bus/usb 10 | {% if tmsetup_pcietpus_confirmed is defined and tmsetup_pcietpus_confirmed is iterable %} 11 | {% for tpu in tmsetup_pcietpus_confirmed %} 12 | - {{ tpu }}:{{ tpu }} 13 | {% endfor %} 14 | {% endif %} 15 | volumes: 16 | - /etc/localtime:/etc/localtime:ro 17 | - {{ tmsetup_codedir }}/frigate/config:/config 18 | - {{ tmsetup_codedir }}/frigate/storage:/media/frigate 19 | - type: tmpfs # Optional: 1GB of memory, reduces SSD/SD Card wear 20 | target: /tmp/cache 21 | tmpfs: 22 | size: 1000000000 23 | ports: 24 | - "5000:5000" 25 | - "1964:1984" # go2rtc api 26 | - "8564:8554" # RTSP feeds 27 | - "8565:8555/tcp" # WebRTC over tcp 28 | - "8565:8555/udp" # WebRTC over udp 29 | env_file: "frigate.env" 30 | -------------------------------------------------------------------------------- /docker-frigate/frigate.env.j2: -------------------------------------------------------------------------------- 1 | # Frigate environment variables 2 | FRIGATE_RTSP_PASSWORD="{{ tmsetup_frigate_rtsp_password }}" 3 | PLUS_API_KEY="" 4 | FRIGATE_MQTT_USER="{{ tmsetup_frigate_mqtt_user }}" 5 | FRIGATE_MQTT_PASSWORD="{{ tmsetup_frigate_mqtt_password }}" 6 | FRIGATE_HOME_DIR="{{ tmsetup_codedir }}/frigate" 7 | FRIGATE_VERSION="{{ tmsetup_frigate_version }}" 8 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/Screenshot_20250306_110924.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/Screenshot_20250306_110924.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/Screenshot_20250307_143756.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/Screenshot_20250307_143756.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/build-it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/build-it.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/icon-diy-350x200px-black-bg_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/icon-diy-350x200px-black-bg_grey.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/icon-shop-350x200px-black-bg_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/icon-shop-350x200px-black-bg_grey.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/store.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/tm-display-pic01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/tm-display-pic01.jpg -------------------------------------------------------------------------------- /docs/.gitbook/assets/tm-roadway-graphic-1920x1080-color-bg_black (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/tm-roadway-graphic-1920x1080-color-bg_black (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/tm-roadway-graphic-1920x1080-color-bg_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/tm-roadway-graphic-1920x1080-color-bg_black.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/tm-roadway-graphic-1920x1080-color-bg_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/tm-roadway-graphic-1920x1080-color-bg_white.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/tm-roadway-graphic-transparent-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/docs/.gitbook/assets/tm-roadway-graphic-transparent-grey.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/tools.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: hand-wave 3 | description: >- 4 | The Traffic Monitor is an open source platform to capture holistic roadway 5 | usage. 6 | --- 7 | 8 | # Welcome to smart city traffic monitoring 9 | 10 | ## Overview 11 | 12 | [TrafficMonitor.ai](https://www.trafficmonitor.ai/), the Traffic Monitor, is an open source smart city traffic monitoring software built with commodity hardware to capture holistic roadway usage. Utilizing edge machine learning object detection and Doppler radar, it counts pedestrians, bicycles, and cars and measures vehicle speeds. 13 | 14 | Find our website at [trafficmonitor.ai](https://www.trafficmonitor.ai) to sign up for the newsletter. 15 | 16 |

Be Counted!

17 | 18 | Our mission is to _democratize the power of AI tools_ to _improve community safety and quality of life_ through the _ethical and transparent use of these capabilities_. 19 | 20 | ## Get Started 21 | 22 | The Traffic Monitor software and hardware are open source and available for anyone to build, modify, improve, and contribute back. We welcome your ideas and contributions at the [Traffic Monitor GitHub](https://github.com/glossyio/traffic-monitor) repository! 23 | 24 |
Getting StartedStart counting!getting-started.md
Help & FAQFind answers to common issues In addition to discussions, issues, and chat.
ContributeContribute back to the open source project
25 | 26 | ## Thank You 🩵 27 | 28 | Made possible thanks to these incredible, unaffiliated projects: 29 | 30 | * [Frigate NVR project](https://github.com/blakeblackshear/frigate) (core Object Detection application) 31 | * [Node-RED](https://nodered.org/) (low-code programming environment) 32 | 33 | We also have integration with the open source [ThingsBoard](https://thingsboard.io/) IoT Platform to monitor your device(s) and share data. 34 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Welcome to smart city traffic monitoring](README.md) 4 | * [Getting Started](getting-started.md) 5 | * [Build Your Own Device (DIY)](build-your-own-device-diy/README.md) 6 | * [Recommended Hardware](recommended-hardware.md) 7 | * [Assembly Instructions](build-your-own-device-diy/assembly-instructions.md) 8 | * [Software Installation](build-your-own-device-diy/software-installation.md) 9 | * [Deployment and Mounting Guide](deployment-and-mounting-guide.md) 10 | * [Setup Guide](setup-guide.md) 11 | 12 | ## Configuration 13 | 14 | * [Config Overview](configuration/overview.md) 15 | * [Frigate Config](configuration/frigate-config.md) 16 | * [Node-RED Config](configuration/node-red-config.md) 17 | 18 | ## Development 19 | 20 | * [Dev Environment](development/dev-environment.md) 21 | * [Contributing](development/contributing.md) 22 | 23 | ## Help & FAQ 24 | 25 | * [Frequently Asked Questions](help-and-faq/frequently-asked-questions.md) 26 | * [Where can I get support?](help-and-faq/where-can-i-get-support.md) 27 | 28 | ## Sensor Payloads 29 | 30 | * [Events Payload](sensor-payloads/events-payload.md) 31 | * [Radar Payload](sensor-payloads/radar-payload.md) 32 | * [Air Quality (AQ) Payload](sensor-payloads/air-quality-aq-payload.md) 33 | -------------------------------------------------------------------------------- /docs/build-your-own-device-diy/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: user-helmet-safety 3 | description: Traffic Monitor details and assembly instructions 4 | --- 5 | 6 | # Build Your Own Device (DIY) 7 | 8 | ## Hardware Assembly 9 | 10 | Start by collecting the [recommended-hardware.md](../recommended-hardware.md "mention"). 11 | 12 | Assemble the components: [assembly-instructions.md](assembly-instructions.md "mention") 13 | 14 | ## Software Setup 15 | 16 | See [software-installation.md](software-installation.md "mention") 17 | 18 | \ 19 | -------------------------------------------------------------------------------- /docs/build-your-own-device-diy/assembly-instructions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Assemble the traffic monitor and get it ready for use 3 | --- 4 | 5 | # Assembly Instructions 6 | 7 | _(coming soon)_ 8 | -------------------------------------------------------------------------------- /docs/build-your-own-device-diy/software-installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Software installation for the Traffic Monitor. 3 | --- 4 | 5 | # Software Installation 6 | 7 | Whether you [.](./ "mention") or buy a pre-made unit, these are the instructions to start from scratch with the open source Traffic Monitor: 8 | 9 | 1. Assemble your device (see [recommended hardware](../recommended-hardware.md) ⚒️). 10 | 2. Install [Raspberry Pi OS](https://www.raspberrypi.com/software/) (Full Install) Bookworm (latest). 11 | * Full Install required for all components to run properly. Lite is missing H.264 codec for libcamera. 12 | * Recommend to [Install using Imager](https://www.raspberrypi.com/documentation/computers/getting-started.html#install-using-imager) instructions and follow the [OS customization](https://www.raspberrypi.com/documentation/computers/getting-started.html#advanced-options) to set up your Wi-Fi credentials and username and password to access the device headless (no monitor or keyboard). 13 | 3. Insert the microSD card and start your device 14 | * First boot may take a few minutes before it is fully online 15 | 4. [Remotely access your device](https://www.raspberrypi.com/documentation/computers/remote-access.html#introduction-to-remote-access) (recommend using SSH) and login to your Raspberry Pi 16 | * Recommend use IP Address in case your router doesn't recognize the hostname you set during setup. 17 | 5. Run `git clone https://github.com/glossyio/traffic-monitor` into your home folder (or any folder) 18 | 6. Run `cd traffic-monitor` 19 | 7. Run `sudo chmod +x script/*` to enable scripts 20 | 8. Run `./script/bootstrap` to fulfill dependencies. _Note_: System will reboot after this script. 21 | 9. After reboot, log into the device and `cd traffic-monitor` to continue. 22 | 10. Run `./script/setup` to set up project in an initial state 23 | 11. Run `./script/server` to start the application 24 | * `./script/update` is not required on initial setup but may be used if you change Docker configurations. This _does not_ yet automatically update the traffic monitor. 25 | 12. Access the application at the following URLs (check container status with `docker ps`): 26 | 1. `http://:1880/ui` is your primary device dashboard, use it to ensure it is capturing events (Node-Red dashboard) 27 | 2. `http://:5000` to view the Frigate interface and make any configuration changes specific to your deployment 28 | 13. Mount your device in a place it can capture the entire roadway in the mounting guide (coming soon). 29 | 14. [Configure your device](https://github.com/glossyio/traffic-monitor#configuration) 30 | 15. Start capturing roadway usage data! 31 | -------------------------------------------------------------------------------- /docs/configuration/frigate-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Configure Frigate object detection on the Traffic Monitor 3 | --- 4 | 5 | # Frigate Config 6 | 7 | Object detection is powered by [Frigate NVR](https://frigate.video/), which provides powerful capabilities for tuning and reviewing events. The Traffic Monitor is not directly affiliated with the Frigate NVR project. 8 | 9 | Refer to [Frigate Configuration](https://docs.frigate.video/configuration/) docs for full list of available configuration options and descriptions. 10 | 11 | ## Recommended Traffic Monitor Settings 12 | 13 | The recommended Traffic Monitor settings attempts to optimize the Frigate config for **object detection on roadways**. Each deployment presents unique scenarios and challenges with accurate and precise object detection. 14 | 15 | View [frigate-config.yml](https://github.com/glossyio/traffic-monitor/blob/main/docker-frigate/frigate-config.yaml) for the sample. 16 | 17 | {% hint style="info" %} 18 | Many settings will need to be uniquely tailored to your specific deployment. See [deployment-and-mounting-guide.md](../deployment-and-mounting-guide.md "mention") for optimizing your placement. 19 | {% endhint %} 20 | 21 | ## Optimizing Object Detection 22 | 23 | You can more easily determine how your object detection is working through Frigate's Debug interface by going to **Frigate > Settings > Debug**. 24 | 25 |

Frigate > Settings > Debug to see how your object detection settings are working

26 | 27 | Fine-tuning object can help you with the following: 28 | 29 | * detection (are you missing bikes or pedestrians?) 30 | * reducing cross-classification (is an ebike being called a motorcycle?) 31 | * minimizing false positives (is a tree being detected as a person?), see also [#defining-masks](frigate-config.md#defining-masks "mention") 32 | 33 | The object detection model accuracy and detection ability may vary depending on a number of factors including mounting conditions such as height and angles to the roadway, camera quality and settings, and environmental conditions such as clouds, rain, snow, etc. 34 | 35 | The generalized model available in the base version works well at a variety of angles, but is particularly suited for an oblique angle that has a good side-view of objects as they pass through the frame. [Frigate object filters](https://docs.frigate.video/configuration/object_filters/#object-scores) have a variety of score and threshold parameters that may be set to be more effective with your deployment. 36 | 37 | ### Sample Object Detection Fine-Tuning 38 | 39 | The most relevant section of the Frigate config for fine-tuning object detection is the following. 40 | 41 | In this sample, bicycle threshold is set very low to detect most types of bikes encountered on the roadway while motorcycle threshold is set high so even large ebikes don't get cross-classified as motorcycles: 42 | 43 | ```yaml 44 | objects: 45 | track: 46 | - bicycle 47 | - person 48 | - car 49 | - motorcycle 50 | - dog 51 | # Optional: filters to reduce false positives for specific object types 52 | filters: 53 | bicycle: 54 | # Optional: minimum width*height of the bounding box for the detected object (default: 0) 55 | min_area: 0 56 | # Optional: maximum width*height of the bounding box for the detected object (default: 24000000) 57 | max_area: 24000000 58 | # Optional: minimum width/height of the bounding box for the detected object (default: 0) 59 | min_ratio: 0.2 60 | # Optional: maximum width/height of the bounding box for the detected object (default: 24000000) 61 | max_ratio: 10.0 62 | # Optional: minimum score for the object to initiate tracking (default: shown below) 63 | min_score: 0.25 64 | # Optional: minimum decimal percentage for tracked object's computed score to be considered a true positive (default: shown below) 65 | threshold: 0.42 66 | motorcycle: 67 | min_area: 0 68 | max_area: 24000000 69 | min_ratio: 0.2 70 | max_ratio: 10.0 71 | min_score: 0.5 72 | threshold: 0.8 73 | ``` 74 | 75 | ## Defining Masks 76 | 77 | Another tool for reducing false-positives, creating private areas, and refining your configuration. To access this capability, log into your Frigate interface and go to [Frigate > Settings > Motion Masks](https://docs.frigate.video/guides/getting_started/#step-5-setup-motion-masks). 78 | 79 | {% hint style="warning" %} 80 | Use masks sparingly. _Over-masking will make it more difficult for objects to be tracked._ See [Frigate masks](https://docs.frigate.video/configuration/masks). 81 | {% endhint %} 82 | 83 | 1. **Motion Masks**: may be designated to prevent unwanted types of motion from triggering detection. 84 | 2. **Object filter masks**: filter out false positives for a given object type based on location. 85 | 86 | For detailed information visit [Frigate > Masks](https://docs.frigate.video/configuration/masks). 87 | -------------------------------------------------------------------------------- /docs/configuration/node-red-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Traffic Monitor Node-RED configuration logic 3 | --- 4 | 5 | # Node-RED Config 6 | 7 | {% hint style="info" %} 8 | This page is for the Traffic Monitor -specific configuration of the Node-RED flows. This controls much of the logic and flow for the traffic monitor but does not control other applications such as Frigate or the operating system. 9 | {% endhint %} 10 | 11 | {% hint style="info" %} 12 | See [frigate-config.md](frigate-config.md "mention") for controlling object detection parameters. 13 | {% endhint %} 14 | 15 | ## Environment File 16 | 17 | The environment file defines variables that can be used during Node-RED start-up in the [settings file](https://nodered.org/docs/user-guide/runtime/settings-file) and within a flows' node properties. 18 | 19 | A sample script can be found at [node-red-project/environment](https://github.com/glossyio/traffic-monitor/blob/main/node-red-project/environment). 20 | 21 | The environment file is loaded by the `systemd` service `node-red.service` that is set up during by the [Node-RED Rapsberry Pi Install script](https://nodered.org/docs/getting-started/raspberrypi). It shall be located in the user node-red directory, by default at `~/.node-red/environment`. Changes to the environment file must be applied by restarting the Node-RED service by executing the command `node-red-restart` in the terminal. 22 | 23 | ```sh 24 | ######## 25 | # This file contains node-red environment variables loaded by node-red.service 26 | # Read more at https://nodered.org/docs/user-guide/environment-variables 27 | # and https://fedoraproject.org/wiki/Packaging:Systemd 28 | # 29 | # This file shall be located at the root node-red directory, usually `~/.node-red` 30 | # this file is loaded by `systemd`, changes can be applied 31 | # by running the command `node-red-restart` in the terminal 32 | # read more at https://nodered.org/docs/getting-started/raspberrypi 33 | # 34 | # Uses: 35 | # - variables can be used in settings.js by calling `process.env.ENV_VAR` 36 | # - node property can be set by calling `${ENV_VAR} 37 | # 38 | ######## 39 | 40 | # traffic monitor open source software release version 41 | TM_VERSION='0.3.0' 42 | 43 | # used in settings.js for credentialSecret 44 | NODE_RED_CREDENTIAL_SECRET='myNodeRED1234' 45 | 46 | # database locations, relative to user directory defined in settings.js 47 | # will be relative path to store SQLite databases 48 | TM_DATABASE_PATH_TMDB='code/nodered/db/tmdb.sqlite' 49 | 50 | # mqtt broker for incoming Frigate events 51 | # Settings below set up the aedes broker node 52 | TM_MQTT_BROKER_HOST='localhost' 53 | TM_MQTT_BROKER_PORT='1883' 54 | # mqtt user, leave blank for no authentication 55 | TM_MQTT_BROKER_USERNAME='' 56 | # mqtt password, leave blank for no authentication 57 | TM_MQTT_BROKER_PASSWORD='' 58 | 59 | # defines system USB serial port for radar 60 | # run `ls -lat /sys/class/tty/ttyACM*` to list devices 61 | TM_RADAR_SERIAL_PORT_00='/dev/ttyACM0' 62 | TM_RADAR_SERIAL_PORT_01='/dev/ttyACM1' 63 | TM_RADAR_SERIAL_PORT_02='/dev/ttyACM2' 64 | TM_RADAR_SERIAL_PORT_03='/dev/ttyACM3' 65 | ``` 66 | 67 | ## Config File 68 | 69 | The Traffic Monitor Node-RED config file changes definitions to various services and functionality. 70 | 71 | The config file is loaded whenever the TM flows restart. It is located in the user node-red directory,`~/.node-red/config.yml`. 72 | 73 | {% hint style="info" %} 74 | It is _not necessary_ to copy this full configuration file. Default values are specified below. 75 | {% endhint %} 76 | 77 | ```yml 78 | ######## 79 | # This file contains configuration settings executed by node-red 80 | # Note: Comments will be removed by updates from node-red 81 | ######## 82 | 83 | # Optional: IoT hub backend integration 84 | thingsboard: 85 | # Optional: enable connection to backend thingsboard server (default: shown below) 86 | enabled: false 87 | # Required: host name, without protocol or port number 88 | host: tb.server.com 89 | # Required: thingsboard telemetry protocol (default: shown below), 90 | # NOTE: only http(s) currently supported, mqtt coming soon 91 | # see https://thingsboard.io/docs/reference/protocols/ 92 | protocol: http 93 | # Optional: port, common settings: https=443, http=80, mqtt=1883 94 | # Check with your ThingsBoard admin for settings 95 | port: 96 | # Optional: API key for device 97 | # Note: (Future) if already provisioned, will be assigned based on provisionDeviceKey and secret 98 | access_token: 99 | # Optional: future use for auto-provisioning (RPiSN) 100 | # provisionDeviceKey: 101 | # Optional: future use for auto-provisioning (manual) 102 | # provisionDeviceSecret: 103 | 104 | # Optional: deployment location details 105 | # Note: May be used to determine device placement on maps 106 | # NOTE: Can be overridden at the sensors level, top-level values will cascade down 107 | deployment: 108 | # NOTE: for address-level accuracy, recommend at least 4 digits after the decimal 109 | # Optional: Latitude in decimal degrees format; e.g. 45.5225 110 | lat: 111 | # Optional: Longitude in decimal degrees format; e.g. -122.6919 112 | lon: 113 | # Optional: cardinal (N, S, E, W) or ordinal (NE, NW, SE, etc.) direction the camera/radar is facing 114 | # Note: For bearing, match the roadway traffic direction 115 | bearing: 116 | 117 | sensors: 118 | # Optional: if used, must match the Frigate camera name(s) 119 | # if not set, no cameras will be used 120 | cameras: 121 | # camera name must match Frigate configuration camera names 122 | picam_h264: 123 | # Optional Enable/disable the camera (default: shown below). 124 | # if disabled, any Frigate events for specified camera will be ignored 125 | # Note: this will not impact Frigate's system 126 | enabled: false 127 | # Optional: define the radar co-located with camera to associate speeds 128 | # camera and radar direction and field of view (FOV) should match 129 | # Note: name needs to match one defined in `radars` section 130 | # Note: A single radar may be attached to multiple cameras 131 | camera_radar: TM_RADAR_SERIAL_PORT_00 132 | 133 | # Optional: used to specify what radars are enabled for speed/direction and detection 134 | radars: 135 | # Note: Names must match those defined in the node-red environment file 136 | # Names are used to associate readings with cameras and other sensors 137 | TM_RADAR_SERIAL_PORT_00: 138 | # Optional: Enable/disable the radar (default: shown below). 139 | enabled: false 140 | 141 | # Optional: used to specify air quality monitor sensor name(s) 142 | # Note: air quality configuration file is separate from the node-red config, based on the aq device 143 | airquality_monitors: 144 | # Required: aq sensor name must match AQ configuration -defined MQTT topic middle element (second element) 145 | sensorname01: 146 | # Optional: Enable/disable the AQ sensor payloads (default: shown below). 147 | enabled: false 148 | # Required: mqtt topic to subscribe for incoming full-payload telemetry from AQ sensor 149 | # must be last element in mqtt topic defined in AQ configuration 150 | mqtt_topic_incoming: readings 151 | 152 | time: 153 | # Optional: Set a timezone to use in the UI (default: use browser local time) 154 | # NOTE: shall be in unix tz format: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 155 | # this will also set the timezone for the entire system 156 | timezone: America/Los_Angeles 157 | # Optional: For internet-connected deployments, sync using `timedatectl set-npt` (default: shown below) 158 | # Note: for offline deployments, time will stop whenever power is disconnected 159 | npt_set: true 160 | ``` 161 | 162 | -------------------------------------------------------------------------------- /docs/configuration/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Traffic Monitor Configuration overview 3 | --- 4 | 5 | # Config Overview 6 | 7 | Traffic Monitor settings and configuration are stored and controlled locally, on each device. 8 | 9 | The following configuration files control the most common operations: 10 | 11 | * [frigate-config.md](frigate-config.md "mention") - to enable and configure object detection 12 | * Node-RED [#environment-file](node-red-config.md#environment-file "mention") - to configure hardware sensors 13 | * Node-RED [#config-file](node-red-config.md#config-file "mention") - to enable and configure sensors and ThingsBoard IoT hub connection 14 | 15 | It is recommended to start with a minimal configuration and add to it as needed. 16 | 17 | ## Backup and Restore 18 | 19 | Backup and restore are built into the Node-RED Device Dashboard but require a connection to a [ThingsBoard](https://thingsboard.io/) IoT (Internet of Things) hub server. 20 | -------------------------------------------------------------------------------- /docs/deployment-and-mounting-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: house-day 3 | description: Permanent and temporary physical placement suggestions. 4 | --- 5 | 6 | # Deployment and Mounting Guide 7 | 8 | Deployment encompasses geographic location and bearing, physical hardware mounting, angle of camera and device to roadway, and configuration to make it ready to detect objects. 9 | 10 | ## Deployment and Mounting 11 | 12 | {% hint style="danger" %} 13 | **Warning:** Ensure compliance with all applicable laws and local regulations when installing, mounting, and deploying a traffic monitor, particularly in public spaces. Unauthorized surveillance can lead to legal consequences and infringement of privacy rights. Always consult with legal professionals or local authorities if you are unsure of the requirements. Information in this guide is for educational purposes, and you are responsible for adhering to applicable laws and consequences of the deployment and use of the traffic monitor. 14 | 15 | Ensure you are mounting the traffic monitor in an approved area to comply with local regulations, and avoid attaching it to utility poles without proper authorization. 16 | {% endhint %} 17 | 18 | ### Temporary 19 | 20 | The traffic monitor may work very well at head-height mounted anywhere with an unobstructed view of the roadway. A sturdy camera tripod works well for this situation. 21 | 22 | \[Image of tripod mount] 23 | 24 | \[Image of other temporary equipment] 25 | 26 | ### Permanent 27 | 28 | {% hint style="warning" %} 29 | **Safety Note**: When installing the camera, always use proper safety equipment, such as gloves and safety goggles and ladder safety to protect yourself. Ensure that the camera is securely mounted, particularly in public spaces, to prevent tampering or accidental damage. Failure to do so could result in injury or damage to property. Verify that all mounting components are tightly fastened, safety tethers are in place, and check for stability to guarantee safe and reliable operation. 30 | {% endhint %} 31 | 32 | ### Choose a mounting location 33 | 34 | \[link to omnipresence installation] 35 | 36 | \[show image of roadway with height, vertical angle, horizontal angle, and calculations] 37 | 38 | ### Angle the traffic monitor 39 | 40 | This will be dependent on the hardware you have chosen to install: 41 | 42 | * **The camera** needs an unobstructed view of the roadway for the best performance, but it is able to perform object detection anywhere in the camera frame. 43 | * **The radar** has a narrower field-of-view (FOV) than most cameras and requires specific angles to the roadway for the most accurate speed measurements. 44 | 45 | \[show image of sample roadway] 46 | 47 | ## Post-deployment Configuration 48 | 49 | ### Configure Zones 50 | 51 | The Traffic Monitor will be expecting a number of [Frigate zones](https://docs.frigate.video/configuration/zones/) to work properly with all dashboards and logic. These need to manually designated, based on your deployment. 52 | 53 | {% hint style="success" %} 54 | Ensure following [Frigate zones](https://docs.frigate.video/configuration/zones/) are manually configured each time a the traffic monitor is re-positioned or relocated, based on your unique deployment . 55 | {% endhint %} 56 | 57 | Access Frigate's interface by going to `http://:5000` and go Settings > Configure > Masks / Zones. 58 | 59 | Set up the following zones: 60 | 61 | * zone\_capture - Set to capture the entire roadway, including sidewalks that are clearly in view for counting objects. 62 | * zone\_near - Paired with `zone_near`, this will determine if an object moves "outbound" or "inbound". Set this to be roughly the further half of the `zone_capture` region. 63 | * zone\_far - Paired with `zone_far`, this will determine if an object moves "outbound" or "inbound". Set this to be roughly the closer half of the `zone_capture` region. 64 | * zone\_radar - (for units equipped with radar) - This should correspond to the field of view for the radar (where it can pick up accurate measurements) on the street. It will roughly make a rectangle in the center of the camera field of view from curb to curb. 65 | 66 |

Properly configured Frigate Zones

67 | 68 | ### Define Masks 69 | 70 | Optional step for reducing false-positives, creating private areas, and refining your configuration. 71 | 72 | {% hint style="warning" %} 73 | Use masks sparingly. _Over-masking will make it more difficult for objects to be tracked._ See [Frigate masks](https://docs.frigate.video/configuration/masks) for more detailed explanation of how masks work and how to use them. 74 | {% endhint %} 75 | 76 | **Motion Masks**: may be designated to prevent unwanted types of motion from triggering detection. 77 | 78 | **Object filter masks**: filter out false positives for a given object type based on location. 79 | 80 | For more information view [Frigate > Setup > Motion Masks](https://docs.frigate.video/guides/getting_started/#step-5-setup-motion-masks) and detailed info at [Frigate > Masks](https://docs.frigate.video/configuration/masks). 81 | 82 | ## Notes 83 | 84 | ### Optimize Object Detection 85 | 86 | The object detection model accuracy and detection ability may vary depending on a number of factors including mounting conditions such as height and angles to the roadway, different cameras and camera settings, and environmental conditions. 87 | 88 | The generalized model available in the base version works well at a variety of angles, but is particularly suited for an oblique angle that has a good side-view of objects as they pass through the frame. [Frigate object filters](https://docs.frigate.video/configuration/object_filters/#object-scores) have a variety of score and threshold parameters that may be set to be more effective with your deployment. 89 | -------------------------------------------------------------------------------- /docs/development/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in getting involved! We welcome all skill levels and abilities as contributions. Visit our [Traffic Monitor GitHub](https://github.com/glossyio/traffic-monitor) page to get involved. 4 | 5 | Contributions can include any of the following plus more! 6 | 7 | * 💡 [Feature requests](https://github.com/glossyio/traffic-monitor/issues/new?assignees=\&labels=\&projects=\&template=feature_request.md\&title=) and use case ideas 8 | * 🐞 [Bug reports](https://github.com/glossyio/traffic-monitor/issues/new?assignees=\&labels=\&projects=\&template=bug_report.md\&title=) 9 | * 👩‍💻 [Code contributions](https://github.com/glossyio/traffic-monitor/blob/main/CONTRIBUTING.md), see Contributing 10 | * 👍 Comment on or thumbs-up [milestones](https://github.com/glossyio/traffic-monitor/milestones) or [issues](https://github.com/glossyio/traffic-monitor/issues) 11 | * 💬 Questions, comments, and videos/images of your deployments on our [Traffic Monitor Zulip Chat](https://trafficmonitor.zulipchat.com/)! 12 | 13 | We foster a safe and welcoming community, review [our Code of Conduct](https://github.com/glossyio/traffic-monitor?tab=coc-ov-file#readme). 14 | -------------------------------------------------------------------------------- /docs/development/dev-environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Set up to contribute back to the Traffic Monitor project 3 | --- 4 | 5 | # Dev Environment 6 | 7 | The Traffic Monitor software is completely open source, so you are welcome to modify your devices to fit your needs. If you think others will benefit from your changes, you are welcome to [join the community](../help-and-faq/where-can-i-get-support.md) and [contribute back](contributing.md)! 8 | 9 | The [Traffic Monitor OSS repo](https://github.com/glossyio/traffic-monitor) is set up as a [monorepo](https://en.wikipedia.org/wiki/Monorepo) containing everything to get the TM up and running. 10 | 11 | ## Node-RED 12 | 13 | [Node-RED](https://nodered.org/) provides the primary logic engine to the Traffic Monitor including: 14 | 15 | * Accepting input from other applications, such as Frigate for object detection, and sensors such as the radar for speed measurement. 16 | * Enriching events by attaching speed 17 | * Saving payloads and data internally 18 | * Sending data to downstream applications 19 | 20 | _(More instructions coming soon)_ 21 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: play 3 | description: Start counting with the Traffic Monitor! 4 | --- 5 | 6 | # Getting Started 7 | 8 | ## Step 1: Get your Traffic Monitor 9 | 10 | The traffic monitor requires the [Recommended Hardware](recommended-hardware.md) and [software installation](build-your-own-device-diy/software-installation.md) before you can begin capturing roadway data. 11 | 12 | There are two ways to get started: 13 | 14 |
Build It! See our Build your own device (DIY) guide. The software is open source and components are available retail.Customize to your heart's content.icon-diy-350x200px-black-bg_grey.pngbuild-your-own-device-diy
Buy It!(coming soon)Purchase a pre-built traffic monitor or kit from the Roadway Biome Project.icon-shop-350x200px-black-bg_grey.png
15 | 16 | ## Step 2: Plan your Deployment 17 | 18 | The traffic monitor must be placed with a good view of the roadway. See the [deployment-and-mounting-guide.md](deployment-and-mounting-guide.md "mention") for more information on the most common deployment types: 19 | 20 | 1. [#temporary](deployment-and-mounting-guide.md#temporary "mention") deployments allow you to set up on the right-of-way or sidewalk next to a roadway to get counts for a short time. 21 | 2. [#permanent](deployment-and-mounting-guide.md#permanent "mention") deployments are geared towards setting up a traffic monitor in a location to get 24/7 counts and monitoring. 22 | 23 | ## Step 3: Set up your Device 24 | 25 | After you have your traffic monitor physically set up (mounted), boot it up and it is ready to go. However, to get the best data and most out of the device, you will need to set up the device: 26 | 27 | 1. Follow the [setup-guide.md](setup-guide.md "mention"). 28 | 2. [frigate-config.md](configuration/frigate-config.md "mention") will guide you in setting up the object detection capabilities by turning on the camera and detection and defining zones. 29 | 3. [node-red-config.md](configuration/node-red-config.md "mention") will turn on additional data collection capabilities, such as pairing your camera with the radar and other sensors, sharing your de-identified event data with another system or data provider, and more! 30 | 31 | ## Step 4: Collect and Share that Data 32 | 33 | The traffic monitor will now run 24/7 collecting the data you set up. It will automatically restart if the power cycles, even if you are not there to reset it. 34 | 35 | You can view the on-device dashboards, review event snapshots and clips in Frigate, download the local database, or view data shared with another data provider. 36 | 37 | There are other projects out there that even pair the Traffic Monitor with physical displays to show daily counts of cars, bikes, and pedestrians. The sky is the limit! Share via the [TM GitHub Show and tell](https://github.com/glossyio/traffic-monitor/discussions/new?category=show-and-tell) or our [TM Zulip chat](https://trafficmonitor.zulipchat.com/). 38 | 39 |

LED Display showing daily counts capture with the traffic monitor

40 | -------------------------------------------------------------------------------- /docs/help-and-faq/frequently-asked-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Troubleshooting tips and tricks 3 | --- 4 | 5 | # Frequently Asked Questions 6 | 7 | -------------------------------------------------------------------------------- /docs/help-and-faq/where-can-i-get-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Get help with the Traffic Monitor 3 | --- 4 | 5 | # Where can I get support? 6 | 7 | We are a volunteer-driven open source project relying on the generosity of our community to answer questions and offer support. While we strive to assist as many as possible, our capacity is limited, and your patience is appreciated. Your involvement is crucial, and by engaging with us, you help foster a supportive and thriving environment where everyone can benefit from shared knowledge and solutions. 8 | 9 | We are also an inclusive community and ask that everyone read and follow our [Code of Conduct](https://github.com/glossyio/traffic-monitor/blob/main/CODE_OF_CONDUCT.md). 10 | 11 | We also welcome your contributions! You make the traffic monitor great. For code, troubleshooting, new ideas and more, see [contributing.md](../development/contributing.md "mention"). 12 | 13 | ## Read the docs 14 | 15 | The [Traffic Monitor docs](https://docs.trafficmonitor.ai) have a wealth of knowledge and are searchable. 16 | 17 | ## Search the project repository 18 | 19 | After the docs the best place to start is the [Traffic Monitor project repository](https://github.com/glossyio/traffic-monitor). Look for the search bar in the _upper right corner_ and then filtering for matching discussions, issues, or even code (filter is on the left side of the screen). 20 | 21 | ## Post on GitHub discussions 22 | 23 | The [Traffic Monitor repo discussions](https://github.com/glossyio/traffic-monitor/discussions) board is the next best place to interact with the community. Hit New Discussion and there are a variety of category templates: 24 | 25 | * 💬 [General](https://github.com/glossyio/traffic-monitor/discussions/new?category=general) - Chat about anything and everything here 26 | * 💡 [Ideas](https://github.com/glossyio/traffic-monitor/discussions/new?category=ideas) - Share ideas for new features, also vote on ideas 27 | * 🗳️ [Polls](https://github.com/glossyio/traffic-monitor/discussions/new?category=polls) - Take a vote from the community 28 | * 🙏 [Q\&A](https://github.com/glossyio/traffic-monitor/discussions/new?category=q-a) - Ask the community for help 29 | * 👐 [Show and tell](https://github.com/glossyio/traffic-monitor/discussions/new?category=show-and-tell) - Show off something you've made 30 | 31 | ## Open a GitHub issue 32 | 33 | To open a [Traffic Monitor GitHub issue](https://github.com/glossyio/traffic-monitor/issues), ensure your topic is specific to a bug, request for a new feature, or a detailed technical question that hasn't been addressed in existing discussions. Make sure to provide enough context and detail to help collaborators address the issue effectively. There are a few issue templates: 34 | 35 | * 🐞 [Bug report](https://github.com/glossyio/traffic-monitor/issues/new?assignees=\&labels=\&projects=\&template=bug_report.md\&title=) - Create a report to help us improve 36 | * 💡 [Feature Request](https://github.com/glossyio/traffic-monitor/issues/new?assignees=\&labels=\&projects=\&template=feature_request.md\&title=) - Suggest an idea for this project 37 | 38 | Sometimes, a GitHub issue may be closed without a full resolution. This does not imply a lack of concern or interest from the maintainers. Issues may be closed for various reasons, such as duplicate reports, inactivity, or because they are being addressed elsewhere. We encourage continuous community engagement and collaboration to ensure all significant matters are acknowledged and acted upon. 39 | 40 | ## Chat with us 41 | 42 | To foster an interactive community, we also have a [Traffic Monitor Zulip chat](https://trafficmonitor.zulipchat.com/). This is often where project developers will discuss code contributions, so it is a good place to get started if you are interested in [contributing.md](../development/contributing.md "mention"). 43 | 44 | -------------------------------------------------------------------------------- /docs/recommended-hardware.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Commodity hardware to enable object detection and speed/direction measurement. 3 | --- 4 | 5 | # Recommended Hardware 6 | 7 | Customize the hardware to fit your needs! The core components include the computing device, storage, camera, and co-processor. Feel free to mix-and-match components but most of the documentation and default configuration assumes using the hardware recommended below. 8 | 9 | {% hint style="info" %} 10 | We are not affiliated with any of the stores or companies linked in this section. These are suggestions that have been used or tested by contributors. If you have used or tested more, post on [TM GitHub discussions](https://github.com/glossyio/traffic-monitor/discussions)! 11 | {% endhint %} 12 | 13 | ## Hardware Check List 14 | 15 | Use the following checklist as a quick guide to components you need to purchase 16 | 17 | * [ ] Computing Device: [Raspberry Pi 5](https://www.raspberrypi.com/products/raspberry-pi-5/) (RPi 5) 4GB/8GB 18 | * [ ] Storage: microSD card (≥32GB), [SanDisk Extreme Pro microSDXC UHS-I Card](https://www.westerndigital.com/products/memory-cards/sandisk-extreme-pro-uhs-i-microsd?sku=SDSQXCD-128G-GN6MA) 19 | * [ ] Power: official RPi [27W USB-C Power Supply](https://www.pishop.us/product/raspberry-pi-27w-usb-c-power-supply-black-us/) 20 | * [ ] Camera: [Raspberry Pi Camera Module 3](https://www.raspberrypi.com/products/camera-module-3/) plus [RPi 5 Camera Cable](https://www.raspberrypi.com/products/camera-cable/) 21 | * [ ] AI co-processor: see [AI Co-processor](recommended-hardware.md#ai-co-processor) 22 | * [ ] Radar (optional): [OmniPreSence OPS243-A Doppler Radar Sensor](https://omnipresense.com/product/ops243-doppler-radar-sensor/) 23 | * [ ] Enclosure: [#enclosure-weather-resistant-box](recommended-hardware.md#enclosure-weather-resistant-box "mention") 24 | 25 | ## Computing Device 26 | 27 | (Required) [Raspberry Pi 5](https://www.raspberrypi.com/products/raspberry-pi-5/) (RPi 5) 4GB/8GB. The Traffic Monitor is designed around 4GB memory profile, but if you have many sensors and other applications running, 8GB may be more performant. 28 | 29 | {% hint style="warning" %} 30 | The Traffic Monitor is based on the Raspberry Pi 5. The Raspberry Pi 4B and earlier units are not recommended as they have experienced detrimental performance due to not meeting the power requirements on the peripherals (USB) for the TPU and radar for this setup. However, many have been successful with earlier versions of the Raspberry Pi for object detection, so your mile may vary. 31 | {% endhint %} 32 | 33 | ### Storage 34 | 35 | (Required) A high-quality microSD card or a SSD (see alternative). Recommend at least 32GB capacity for system files with minimal (or no) snapshot and video capture. 36 | 37 | 1. _Option_: Setup has been tested and works well with the [SanDisk Extreme Pro microSDXC UHS-I Card](https://www.westerndigital.com/products/memory-cards/sandisk-extreme-pro-uhs-i-microsd?sku=SDSQXCD-128G-GN6MA). 38 | 2. _Option_: [Raspberry Pi official SD Card](https://www.raspberrypi.com/products/sd-cards/?variant=sd-64gb) should perform particularly well but sizes only range up to 128GB. 39 | 3. _Alternative_: There are many options on the RPi5 to use a faster, more durable NVME (M.2) drive, including those that pair with the Coral TPU, such as the Pineboards [HatDrive AI! Coral TPU bundle](https://pineboards.io/products/hatdrive-ai-coral-edge-tpu-bundle-nvme-2230-2242-gen-2-for-raspberry-pi-5). 40 | 41 | ### Power 42 | 43 | (Required) To run the Traffic Monitor and components. 44 | 45 | {% hint style="warning" %} 46 | The Raspberry Pi 5 is rated for 27-watts (5V at 5A) and using anything with a lower rating like the older RPi PSUs will often result in resets and/or throttling. However, the Traffic Monitor typically consumes between 6-14-watts of energy when it is fully operational and inferencing, depending on number of components in use and how much motion is detected. 47 | {% endhint %} 48 | 49 | 1. _Recommended Option_: The official [27W USB-C Power Supply](https://www.pishop.us/product/raspberry-pi-27w-usb-c-power-supply-black-us/) for testing and permanent mounts. 50 | 2. _Alternative_: PoE (Power over Ethernet) HATs available for the RPi 5. Raspberry Pi Foundation has not yet released an official one, but if you have a working solution suggest it in the [TM GitHub Discussion](https://github.com/glossyio/traffic-monitor/discussions/new/choose)! 51 | 52 | ## Camera(s) 53 | 54 | (Required) The official, connected Raspberry Pi cameras are below recommended for compact, local object detection; however any camera that can output H.264 is conceivably compatible with the traffic monitor, so you may attach USB or even networked cameras. See more at [Frigate's recommended camera hardware](https://docs.frigate.video/frigate/hardware#cameras) for alternatives. 55 | 56 | 1. _Recommended Option_: [Raspberry Pi Camera Module 3](https://www.raspberrypi.com/products/camera-module-3/) (requires a [RPi 5 Camera Cable](https://www.raspberrypi.com/products/camera-cable/) that is not included). 57 | 2. _Alternative/additional:_ [Raspberry Pi Global Shutter](https://www.raspberrypi.com/products/raspberry-pi-global-shutter-camera/) for faster motion capture and custom-lens based on your needs (requires a [RPi 5 Camera Cable](https://www.raspberrypi.com/products/camera-cable/) that is not included). 58 | 59 | The Raspberry Pi 5 has 2 camera transceiver slots, so you can easily attach 2 native Raspberry Pi cameras to the board. 60 | 61 | {% hint style="info" %} 62 | See the [Frigate camera setup](https://docs.frigate.video/frigate/camera_setup) for more information on tuning stream configurations based on various goals for your deployment. 63 | {% endhint %} 64 | 65 | ## AI Co-processor 66 | 67 | (Required with camera) The AI co-processor is an efficient way to run the object detection model, much more efficient than CPU-alone. 68 | 69 | {% hint style="info" %} 70 | The AI co-processor is used by Frigate to run the object detection model, see Frigate's [supported hardware](https://docs.frigate.video/configuration/object_detectors) for more options and details. 71 | {% endhint %} 72 | 73 | [Coral AI Tensor Processing Unit (TPU)](https://coral.ai/products/). The Coral TPU is capable of 100+ FPS with millisecond inference time. Other co-processors may work, but the Coral TPU is fully supported with [Frigate object detectors](https://docs.frigate.video/configuration/object_detectors) out of the box. 74 | 75 | 1. _Easiest Option_: [Coral USB Accelerator](https://coral.ai/products/accelerator) is easy-to-use co-processor that you can connect to any computing device with a USB interface. 76 | 2. _Alternative_: Coral HATs (Hardware-Attached-on-Top \[of a Raspberry Pi]) are more compact, upgradable, and usually cheaper: 77 | * [Rapsberry Pi M.2 HAT+](https://www.raspberrypi.com/products/m2-hat-plus/) pairs nicely with the [Coral M.2 Accelerator A+E Key](https://coral.ai/products/m2-accelerator-ae). 78 | * Pineboards offers the [Hat AI! Coral TPU bundle](https://pineboards.io/products/hat-ai-coral-edge-tpu-bundle-for-raspberry-pi-5) that connects via PCIe that offers a sleek way to add the Coral capabilities with an additional slot for an M.2 SSD. 79 | 3. _Alternative_: [Raspberry Pi AI HAT+](https://www.raspberrypi.com/products/ai-hat/) with Hailo-8L offers high-performance, power-efficient processing. 80 | 81 | ## Radar 82 | 83 | (Recommended) [OmniPreSence OPS243-A Doppler Radar Sensor](https://omnipresense.com/product/ops243-doppler-radar-sensor/) - provides accurate radar-based speed/direction detection up to 100-meters away. _Planned future capability_ of object detection with the radar, which will be enabled with a software update. 84 | 85 | ## Other Sensors 86 | 87 | (Optional) Air Quality sensor, [Enviro+](https://www.pishop.us/product/enviro-for-raspberry-pi/), with [Particulate Matter (PM) Sensor](https://www.pishop.us/product/pms5003-particulate-matter-sensor-with-cable/). 88 | 89 | ## Enclosure (weather-resistant box) 90 | 91 | * _Purchase_: _(coming soon)_ Purchase the box or a kit to assemble yourself. 92 | * _Print it yourself_: We offer a 3D printable model so you can build the truly open source Traffic Monitor. Visit our open source repository [greendormer/tm-enclosure-3d](https://github.com/greendormer/tm-enclosure-3d) for details and parts list. 93 | * _Alternative DIY_: There are many waterproof electrical junction boxes that may be modified to fit your needs with the traffic monitor. Rough dimensions to fit the Traffic Monitor components including the camera and radar should be around 9"x7"x4" such as the [TICONN IP67 ABS Enclosure](https://www.amazon.com/gp/product/B0B87X944Z). 94 | -------------------------------------------------------------------------------- /docs/sensor-payloads/air-quality-aq-payload.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Environment and air quality sensor for the Traffic Monitor 3 | --- 4 | 5 | # Air Quality (AQ) Payload 6 | 7 | ## Overview 8 | 9 | The air quality monitor enables collection of a variety of environmental measurements including gasses commonly associated with pollution, temperature, pressure, humidity, and much more. 10 | 11 | {% hint style="info" %} 12 | The AQ software is available at [greendormer/enviroplus-monitor](https://github.com/greendormer/enviroplus-monitor). It is based on the wonderful work from the [roscoe81/enviro-monitor](https://github.com/roscoe81/enviro-monitor) and [pimoroni/enviroplus](https://github.com/pimoroni/enviroplus-python) projects. 13 | {% endhint %} 14 | 15 | ### Hardware 16 | 17 | The following hardware has been tested and incorporated into the Traffic Monitor. 18 | 19 | {% hint style="info" %} 20 | Although we strive to include high-quality equipment and data collection into our application, we make no warranty on the veracity or quality of the hardware or data. We welcome those with an [environmental science](https://en.wikipedia.org/wiki/Environmental_science) background to [contribute](../development/contributing.md)! 21 | {% endhint %} 22 | 23 | * [Enviro for Raspberry Pi](https://www.pishop.us/product/enviro-for-raspberry-pi/) – **Enviro + Air Quality** 24 | * Enviro for Raspberry Pi – **Enviro + Air Quality** 25 | * Air quality (pollutant gases and particulates\*), temperature, pressure, humidity, light, and noise 26 | * [Getting started](https://learn.pimoroni.com/article/getting-started-with-enviro-plus) 27 | * [PMS5003 Particulate Matter Sensor](https://www.pishop.us/product/pms5003-particulate-matter-sensor-with-cable/) for Enviro 28 | * Monitor air pollution cheaply and accurately with this matchbox-sized particulate matter (PM) sensor from Plantower! 29 | * It senses particulates of various sizes (PM1, PM2.5, PM10) from sources like smoke, dust, pollen, metal and organic particles, and more. 30 | 31 | ### Software 32 | 33 | The AQ software is available at [greendormer/enviroplus-monitor](https://github.com/greendormer/enviroplus-monitor) as a Python service script that communicates with the Traffic Monitor Node-RED flow via MQTT messages. See the repository for installation and setup instructions. 34 | 35 | #### config.json 36 | 37 | See [Config Readme](https://github.com/greendormer/enviroplus-monitor/blob/main/config_readme.md) for a detailed description of every available key. 38 | 39 | #### Recommended config settings 40 | 41 | The following are important keys for the recommended default Traffic Monitor -specific configuration: 42 | 43 | * `"enable_send_data_to_homemanager": true` in order to send MQTT payloads to specified broker 44 | * `"mqtt_broker_name": "localhost"` to send to Node-RED MQTT broker (assumes port 1883) 45 | * `"indoor_outdoor_function": "Outdoor"` to utilize `outdoor_mqtt_topic` 46 | * `"enable_display": false` since the AQ sensor will be in an enclosure 47 | * `"outdoor_mqtt_topic": "aq/sensorname01/readings"` for sending messages, must start with "aq" and the middle element, "sensorname01" must be defined in your TM config 48 | * `"long_update_delay": 300` for time between sending MQTT messages (default 300-seconds) 49 | 50 | #### Deployment-specific config settings 51 | 52 | The following location-based settings need to be set per-deployment for your location. They are utilized by the [`astral` package](https://astral.readthedocs.io/en/latest/package.html) for calculating the times of various aspects of the sun and phases of the moon (lat/lon, time zone) and calibrating temperature, humidity, barometer, and gas (altitude) readings. 53 | 54 | ```json 55 | { 56 | "altitude": 49, 57 | "city_name": "Portland", 58 | "time_zone": "America/Los_Angeles", 59 | "custom_locations": [ 60 | "Portland, United States of America, America/Los_Angeles, 45.52, -122.681944" 61 | ] 62 | } 63 | ``` 64 | 65 | ## Air Quality MQTT Incoming Payload 66 | 67 | The TM AQ application sends messages via MQTT integration on the `aq/readings` topic. 68 | 69 | {% hint style="warning" %} 70 | The sensor needs to stabilize (default 5-minutes) after the script initializes before it will send external updates (via MQTT). This is defined by `startup_stabilisation_time` in config.json. 71 | {% endhint %} 72 | 73 | ```json 74 | { 75 | "gas_calibrated": false, 76 | "bar": [ 77 | 1009.44, 78 | "0" 79 | ], 80 | "hum": [ 81 | 28.7, 82 | "2" 83 | ], 84 | "p025": 0, 85 | "p10": 0, 86 | "p01": 0, 87 | "dew": 2.1, 88 | "temp": 21, 89 | "temp_min": 20.9, 90 | "temp_max": 21.1, 91 | "gas_red": 4.2, 92 | "gas_oxi": 0.15, 93 | "gas_nh3": 0.69, 94 | "lux": 1.2, 95 | "proximity": 255, 96 | "lux_raw": 1.16185, 97 | "temp_raw": 28.68982029794099, 98 | "bar_raw": 1003.7122773175154, 99 | "hum_raw": 18.31337919009301, 100 | "gas_red_raw": 140805, 101 | "gas_oxi_raw": 103585, 102 | "gas_nh3_raw": 227871, 103 | "current_time": 1738698665.077546 104 | } 105 | ``` 106 | 107 | MQTT attribute details: 108 | 109 | | Key | Valid Values | Units | Notes | 110 | | --------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 111 | | gas\_calibrated | true/false | | `gas_sensors_warmup_time = 6000` or `startup_stabilisation_time` when `reset_gas_sensor_calibration = true` | 112 | | bar | \[REAL, TEXT] | hPa, Comfort-level `{"0": "Stable", "1": "Fair", "3": "Poorer/Windy/", "4": "Rain/Gale/Storm"}` | Air pressure, compensated for altitude and temp as `Bar / comp_factor` where `comp_factor = math.pow(1 - (0.0065 * altitude/(temp + 0.0065 * alt + 273.15)), -5.257)` | 113 | | hum | \[REAL, TEXT] | %, Comfort-level `{"good": "1", "dry": "2", "wet": "3"}` | Adjusted for compensation factor set in `config.json` | 114 | | Forecast | {OBJECT} | `Valid`: true/false, `3 Hour Change` is millibars difference in barometer readings, `Forecast` is description calculated from barometer change | Calculated forecast based on sensor barometer changes | 115 | | pm01 | REAL | ug/m3 (microgram per meter cubed, µg/m³) | Particulate Matter 1 micrometers / microns (PM1, PM1), Read directly using the `pms5003.pm_ug_per_m3()` method from the particulate matter sensor. | 116 | | pm025 | REAL | ug/m3 (microgram per meter cubed, µg/m³) | Particulate Matter 2.5 micrometers / microns (PM2.5, PM2.5), read directly using the `pms5003.pm_ug_per_m3()` method from the particulate matter sensor. | 117 | | pm10 | REAL | ug/m3 (microgram per meter cubed, µg/m³) | Particulate Matter 10 micrometers / microns (PM10, PM10), Read directly using the `pms5003.pm_ug_per_m3()` method from the particulate matter sensor. | 118 | | dew | REAL | C | Calculated from Temp and Hum as `(237.7 * (math.log(dew_hum/100)+17.271*dew_temp/(237.7+dew_temp))/(17.271 - math.log(dew_hum/100) - 17.271*dew_temp/(237.7 + dew_temp)))` | 119 | | temp | REAL | C | Adjusted for compensation factor set in `config.json` | 120 | | temp\_min | REAL | C | Minimum temperature measured while sensor was running (only resets on restart) | 121 | | temp\_max | REAL | C | Maximum temperature measured while sensor was running (only resets on restart) | 122 | | gas\_red | REAL | ppm | Red PPM calculated as `red_in_ppm = math.pow(10, -1.25 * math.log10(red_ratio) + 0.64)`. `red_ratio` is compensated gas value, see Software notes. | 123 | | gas\_oxi | REAL | ppm | Oxi PPM calculated as `oxi_in_ppm = math.pow(10, math.log10(oxi_ratio) - 0.8129)`. `oxi_ratio` is compensated gas value, see Software notes. | 124 | | nh3 | REAL | ppm | NH3 PPM calculated as `nh3_in_ppm = math.pow(10, -1.8 * math.log10(nh3_ratio) - 0.163)`. `nh3_ratio` is compensated gas value, see Software notes. | 125 | | lux | REAL | lux | Read directly using the `ltr559.get_lux()` method from the light sensor. | 126 | | temp\_raw | REAL | C | Read directly from sensor absent compensation. | 127 | | bar\_raw | REAL | C | Read directly from sensor absent compensation. | 128 | | hum\_raw | REAL | % | Read directly from sensor absent compensation. | 129 | | gas\_red\_raw | REAL | Ohms | Read directly from sensor using `gas_data.reducing` method absent compensation. | 130 | | gas\_oxi\_raw | REAL | Ohms | Read directly from sensor using `gas_data.oxidising` method absent compensation. | 131 | | gas\_nh3\_raw | REAL | Ohms | Read directly from sensor using `gas_data.nh3` method absent compensation. | 132 | | current\_time | REAL | Unix time in Seconds | Created by script upon reading values. | 133 | 134 | ## Air Quality Database 135 | 136 | The following attributes are captured, by default, into `tmdb.events.sqlite`: 137 | 138 | | Attribute | Description | SQLite data type | Valid values | 139 | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | ---------------------------------------------- | 140 | | entryDateTime | Unix timestamp when data capture began on sensor | REAL | Unix timestamp in Seconds | 141 | | gas\_calibrated | Indicates if gas sensors are fully "warmed up". Will be false until \`gas\_sensors\_warmup\_time\` is met (default 10-minutes after sensor starts) | REAL | BOOLEAN, 1 = true / 0 = false | 142 | | temp | Temperature reading in degree Celsius measured directly from BME280 sensor with compensation factor set from device config. | REAL | | 143 | | bar | Barometer air pressure reading in bars (hPa) measured directly from BME280 sensor with compensation set for altitude from device config. | REAL | | 144 | | hum | Humidity reading in percent (%) measured directly from BME280 sensor with compensation factor set from device config. | REAL | | 145 | | dew | Calculated dew point in degree Celsius, based on temperature and humidity using the following calculation (Python) \`(237.7 \* (math.log(dew\_hum/100)+17.271\*dew\_temp/(237.7+dew\_temp))/(17.271 - math.log(dew\_hum/100) - 17.271\*dew\_temp/(237.7 + dew\_temp)))\` | REAL | | 146 | | temp\_raw | Temperature reading in degree Celsius measured directly from BME280 sensor absent of any compensation (raw values). | REAL | | 147 | | bar\_raw | Barometer air pressure reading in bars (hPa) measured directly from BME280 sensor absent of any compensation (raw values). | REAL | | 148 | | hum\_raw | Humidity reading in percent (%) measured directly from BME280 sensor absent of any compensation (raw values). | REAL | | 149 | | pm01 | Particulate Matter (PM) at 1 micrometers or greater in diameter in micrograms per cubic meter (ug/m3) measured directly from PM sensor. | REAL | 0-infinity | 150 | | pm025 | Particulate Matter (PM) at 2.5 micrometers or greater in diameter in micrograms per cubic meter (ug/m3) measured directly from PM sensor. | REAL | 0-infinity | 151 | | pm10 | Particulate Matter (PM) at 10 micrometers or greater in diameter in micrograms per cubic meter (ug/m3) measured directly from PM sensor. | REAL | 0-infinity | 152 | | gas\_red | Reducing gases (RED) reading in Parts Per Million (PPM) measured directly from gas sensor with compensation factor set for drift. Eg hydrogen, carbon monoxide | REAL | 0-infinity | 153 | | gas\_oxi | Oxidising gases (OX) reading in Parts Per Million (PPM) measured directly from gas sensor with compensation factor set for drift. Eg chlorine, nitrous oxide | REAL | 0-infinity | 154 | | gas\_nh3 | Ammonia (NH3) reading in Parts Per Million (PPM) measured directly from gas sensor with compensation factor set for drift. Gas resistance for nh3/ammonia | REAL | 0-infinity | 155 | | gas\_red\_raw | Reducing gases (RED) reading in Ohms measured directly from gas sensor absent of any compensation (raw values). Eg hydrogen, carbon monoxide | REAL | 0-infinity | 156 | | gas\_oxi\_raw | Oxidising gases (OX) reading in Ohms measured directly from gas sensor absent of any compensation (raw values). Eg chlorine, nitrous oxide | REAL | 0-infinity | 157 | | gas\_nh3\_raw | Ammonia (NH3) reading in Ohms measured directly from gas sensor absent of any compensation (raw values). Gas resistance for nh3/ammonia | REAL | 0-infinity | 158 | | lux | Lux reading in Lux measured directly from optical sensor with proximity-adjusted minimum. | REAL | 0.01 to 64k lux | 159 | | lux\_raw | Lux reading in Lux measured directly from optical sensor. | REAL | 0.01 to 64k lux | 160 | | proximity | Proximity reading measure directly from optical sensor. | REAL | 0-infinity | 161 | | sensorName | Air Quality sensor that captured the data. This field may be used to associate with other sensors. | TEXT | | 162 | | deployment\_id | Each ID represents a unique deployment configuration and/or location for the device. This acts a foreign key link to the \`deployment\` table, \`id\` column. | TEXT, FOREIGN KEY | \`deployment\`.\`id\` foreign key, may be null | 163 | 164 | ## Notes on Air Quality readings 165 | 166 | ### Gas sensor 167 | 168 | The [MICS6814](https://www.sgxsensortech.com/content/uploads/2015/02/1143_Datasheet-MiCS-6814-rev-8.pdf) analog gas sensor: _The MiCS-6814 is a robust MEMS sensor for the detection of pollution from automobile exhausts and for agricultural/industrial odors._ 169 | 170 | The sensor includes the ability to detect reductive (RED), oxidative (OXI), and ammonia (NH3) gases. The raw gas readings are measured as Ohms of resistance for their respective gasses, but the software compensates for temperature, humidity, altitude, and drift to provide PPM (parts per million) equivalents.\* 171 | 172 | \*See Software notes and additional discussions on [pimoroni/enviroplus-python #47](https://github.com/pimoroni/enviroplus-python/issues/47) and [pimoroni/enviroplus-python #67](https://github.com/pimoroni/enviroplus-python/issues/67). 173 | 174 | **Software notes** 175 | 176 | * Gas calibrates using Temp, Humidity, and Barometric Pressure readings. 177 | * Gas Sensors (`Red`, `Oxi`, `NH3`) take 100-minutes to warm-up and readings to become available 178 | * To compensate for gas sensor drift over time, the software calibrates gas sensors daily at time set by `gas_daily_r0_calibration_hour`, using average of daily readings over a week if not already done in the current day and if warm-up calibration is completed. This compensates for gas sensor drift over time 179 | * Raw gas readings will also have compensation factors applied, determined by [regression analysis](https://github.com/roscoe81/enviro-monitor/blob/master/Regression_Analysis/Northcliff_Enviro_Monitor_Regression_Analyser.py). 180 | 181 | ### Temperature, pressure, and humidity 182 | 183 | The [BME280](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf) temperature, pressure, humidity sensor with I2S digital output. 184 | 185 | #### Software notes 186 | 187 | * `Temp` (temperature) and `Hum` (humidity) have cubic polynomial compensation factors applied to raw readings 188 | * `Min Temp`and `Max Temp`are calculated over the entire time the script is running 189 | * `Bar`(Barometer) reading updates only every 20 minutes 190 | * Air pressure reading has an altitude compensation factor applied (defined in config.json) 191 | * `Dew`(Dew Point) is calculated from temperature and humidity using the following calculation: 192 | 193 | ```python 194 | dewpoint = (237.7 * (math.log(dew_hum/100)+17.271*dew_temp/(237.7+dew_temp))/(17.271 - math.log(dew_hum/100) - 17.271*dew_temp/(237.7 + dew_temp))) 195 | ``` 196 | 197 | ### Optical (light, proximity) 198 | 199 | The [LTR-559](https://optoelectronics.liteon.com/upload/download/DS86-2013-0003/LTR-559ALS-01_DS_V1.pdf) light and proximity sensor 200 | 201 | ### Noise 202 | 203 | MEMS microphone ([datasheet](https://media.digikey.com/pdf/Data%20Sheets/Knowles%20Acoustics%20PDFs/SPH0645LM4H-B.pdf)). 204 | 205 | ### Particulate matter (PM) 206 | 207 | The Plantower [PMS5003](http://www.aqmd.gov/docs/default-source/aq-spec/resources-page/plantower-pms5003-manual_v2-3.pdf) Particulate Matter (PM) Sensor. 208 | -------------------------------------------------------------------------------- /docs/sensor-payloads/events-payload.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Object detection event payload 3 | --- 4 | 5 | # Events Payload 6 | 7 | ## Overview 8 | 9 | Traffic Monitor object detection events are generated by the connected sensors by performing [object detection](https://en.wikipedia.org/wiki/Object_detection) to identify instances of roadway users such as cars, bikes, pedestrians, and more. Additional data and metadata may also be added via other sensors and processes to create an event payload. 10 | 11 | ### Hardware and Software 12 | 13 | **The camera** is the primary detection method, powered by [Frigate NVR](https://frigate.video/). Events are created by optical camera observation and machine learning-powered [object detectors](https://docs.frigate.video/configuration/object_detectors) inference the frames to label [available objects](https://docs.frigate.video/configuration/objects). 14 | 15 | _Future feature_: **The radar** may also generate object detection events. This is particularly useful for nighttime and low-light conditions or even deployments that do not utilize a camera. See [radar-payload.md](radar-payload.md "mention") for more information on radar readings. 16 | 17 | ## Frigate MQTT Incoming Payload 18 | 19 | Events are recorded via the [Frigate MQTT](https://docs.frigate.video/integrations/mqtt) integration on the `frigate/events` topic containing `type: "end",`which indicates a complete capture event. See Frigate documentation for a full list of available attributes. 20 | 21 | ## Events Database 22 | 23 | The following attributes are captured, by default, into `tmdb.events.sqlite`: 24 | 25 |
AttributeDescriptionSQLite data typeValid values
idUUID for object detection. Will be generated by `source`
Frigate-generated from MQTT event `end`.
Radar-generated from flow.
TEXT, PRIMARY KEYFrigate: Unix timestamp in seconds concatenated to a hyphen and randomly-generated 6-alphanumeric value; e.g. "1721144705.617111-fg3luy"
Radar: Unix timestamp in seconds concatenated to a hyphen and randomly-generated 8-alphanumeric value concatenated with a hypen r '-r'; e.g. "1721144705.617111-fg3luy4x-r"
cameraName of camera for object detection (defined in Frigate config).
Frigate-generated from MQTT event `end`.
TEXTFree-text
labelObject label assigned by Frigate.
Frigate-generated from MQTT event `end`.
TEXTAssigned by model, based on Frigate available objects
sub_labelAdditional informatoin assigned to Frigate event.
Frigate-generated from MQTT event `end`.
TEXTAssigned via Frigate HTTP API
top_scoreModel inference score for object label. This is the highest score as object moved through field of view.
Frigate-generated from MQTT event `end`.
REAL0-1 value
frame_timeUnix timestamp in seconds for when the object was optimally identified by Frigate for the field of view.
Frigate-generated from MQTT event `end`.
REALUnix timestamp in Seconds
start_timeUnix timestamp in seconds for when the object first entered the field of view.
Frigate-generated from MQTT event `end`.
REALUnix timestamp in Seconds
end_timeUnix timestamp in seconds for when the object exited the field of view.
Frigate-generated from MQTT event `end`.
REALUnix timestamp in Seconds
entered_zonesJSON array list of zones in order the object entered each zone. Specified zones are used for various calculations.
Frigate-generated from MQTT event `end`.
TEXTFree-text via Frigate; expected:
- "zone_capture" - region for counting objects
- "zone_radar" - radar detection field of view (FOV)
- "zone_near" - area closest to radar, for determining visual direction
- "zone_far" - area furthest from radar, for determining visual direction
scoreModel inference score for the object to initiate tracking. Computed score as object moves through field of view.
Frigate-generated from MQTT event `end`.
REAL0-1 value
areaWidth*height of the bounding box for the detected object
Frigate-generated from MQTT event `end`.
REAL0-24000000
ratioWidth/height of the bounding box for the detected object; e.g. 0.5 is tall (twice as high as wide box)
Frigate-generated from MQTT event `end`.
REAL0-24000000
motionless_countNumber of frames the object has been motionless
Frigate-generated from MQTT event `end`.
REALInteger count; e.g. 0
position_changesNumber of times the object has changed position
Frigate-generated from MQTT event `end`.
REALInteger count; e.g. 2
attributesAttributes with top score that have been identified on the object at any point
Frigate-generated from MQTT event `end`.
TEXT, JSONJSON object with key:value pairs; e.g. {"face": 0.86}
direction_calcAssigned object moving direction relative to device placement; i.e. "outbound" is moving away from device.TEXT"outbound" or "inbound"
speed_calcAssigned speed/velocity calculated for entire time object was in the camera's field of view.REALPositive, Negative corresponding to inbound and outbound direction, respectively
provenanceSource(s) of event detection. List any sensor on the device that captured or created this event. The first item in the array is considered the primary source.TEXT, JSONJSON Array with every sensor that confirms the same event. e.g. Camera sensor: frigate, Radar sensor: radar
radarNameRadar sensor name that is associated (via config) with the camera during the event, regardless if event was confirmed by radar (see provenance).TEXTFree-text, defined from configs
deployment_idEach ID represents a unique deployment configuration and/or location for the device. This acts a foreign key link to the `deployment` table, `id` column.TEXT, FOREIGN KEY`deployment`.`id` foreign key, may be null
26 | 27 | ## Telemetry 28 | 29 | ### HTTP Telemetry 30 | 31 | [ThingsBoard HTTP upload telemetry API](https://thingsboard.io/docs/reference/http-api/#telemetry-upload-api) requests are sent for each event as a single JSON payload containing: 32 | 33 | * `ts: frame_time * 1000` - to make it milliseconds 34 | * `values: {event:values}`- contains all attributes in [#events-database](events-payload.md#events-database "mention") 35 | 36 | ### MQTT Publications 37 | 38 | The primary event are available on-device for downstream subscriptions (e.g. Home Assistant): 39 | 40 | * **Topic**: `tm/event` 41 | * **Payload**: Same as the [#events-database](events-payload.md#events-database "mention") 42 | 43 | Additionally, there is a daily cumulative object count utilized for the on-device dashboard and connected displays: 44 | 45 | * **Topic**: `tm/events` 46 | * **Payload**: Daily cumulative counts of detected objects (resets at 0400 local time). Note, these can be adjusted in the Frigate config for [Available Objects](https://docs.frigate.video/configuration/objects). 47 | * `car` 48 | * `person` 49 | * `bicycle` 50 | * `motorcycle` 51 | * `bicycle_adj` - bicycle plus motorcycle - adjust for eBikes, but this is addressed now by Frigate configs for each object and is unnecessary for most deployments 52 | * `person_adj` - person minus bicycle - Essentially represents "pedestrian count" since every bicycle will have \[at least one] rider that is detected independently of the bike. 53 | * Scooters and other transit modes will likely only count as a person using the base model. 54 | * Cars never have a person identified inside of them with the base model. 55 | * `dog` 56 | * `cat` 57 | -------------------------------------------------------------------------------- /docs/sensor-payloads/radar-payload.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Doppler radar payloads 3 | --- 4 | 5 | # Radar Payload 6 | 7 | ## Overview 8 | 9 | The Doppler radar enables speed and direction measurement to be collected and added to events. 10 | 11 | ### Hardware 12 | 13 | {% hint style="info" %} 14 | See [#radar](../recommended-hardware.md#radar "mention") for more information on selecting a radar unit. 15 | {% endhint %} 16 | 17 | The [OPS243-A](https://omnipresense.com/product/ops243-doppler-radar-sensor/) radar from [OmniPreSense](https://omnipresense.com/): 18 | 19 | > OmniPreSense’s OPS243 is complete short-range radar (SRR) solution providing motion detection, speed, direction, and range reporting. All radar signal processing is done on board and a simple API reports the processed data. 20 | 21 | ### Software 22 | 23 | The OPS243 radar sensors include an easy-to-use [API interface](https://omnipresense.com/wp-content/uploads/2023/06/AN-010-Y_API_Interface.pdf) for returning a variety of radar readings and calculated values in JSON format. By default, we capture all of these values in separate tables: 24 | 25 | * **DetectedObjectVelocity** (command `ON`). 26 | * [#radar\_dov-table](radar-payload.md#radar_dov-table "mention") 27 | * Sensor determines if an object is present by looking for 2 consecutive speed reports. If met, the max speed detected is reported. If a faster speed is detected, additional speeds are reported. This lowers the number of speed reports for a given detected object. Use On to turn the mode off. 28 | * **TimedSpeedCounts** (command @O). 29 | * [#radar\_timed\_speed\_counts-table](radar-payload.md#radar_timed_speed_counts-table "mention"). 30 | * Sensor counts and reports the cumulative number of objects (defined by DetectedObjectVelocity) that have gone by in a given period. Default TM setting is reporting every 300-seconds. 31 | * **Raw Speed Magnitude** (command `OS`). 32 | * [#radar\_raw\_speed\_magnitude-table](radar-payload.md#radar_raw_speed_magnitude-table "mention") and [#radar\_raw\_speed\_magnitude\_single-table](radar-payload.md#radar_raw_speed_magnitude_single-table "mention") 33 | * Reports magnitude and associated speed of each reading. The magnitude is a measure of the size, distance, and reflectivity of the object detected. By default, TM captures the 3-burst speed/magnitude pairs and the single strongest magnitude and associated speed in separate tables for deep dive and easier analysis, respectively. 34 | * **Vehicle Length** (command `OC`). 35 | * Note: _Requires Firmware OPS9243._ 36 | * [#radar\_oc\_payload-table](radar-payload.md#radar_oc_payload-table "mention") 37 | * From the docs: _Provides several parameters which can help identify the vehicle type and/or lane in which the vehicle is located_. This includes start/end time, frames, min/max MPH, magnitude, and length calculations. 38 | 39 | ## Radar Database 40 | 41 | The following attributes are captured, by default, into `tmdb.events.sqlite`: 42 | 43 | ### radar\_dov table 44 | 45 | | Attribute | Description | SQLite data type | Valid values | 46 | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------- | 47 | | time | Unix timestamp directly from radar | REAL | Unix timestamp in Seconds | 48 | | unit | Unit of measure for velocity/speed set on radar (configurable) | TEXT | "mph" | 49 | | direction | Object moving direction relative to radar placement; i.e. "outbound" is moving away from radar. | TEXT | "outbound" or "inbound" | 50 | | velocity |

Maximum speed/velocity calculated for all measurements object was detected in the radar zone.
`DetectedObjectVelocity` from API

| REAL | Integer, Positive, Negative corresponding to inbound and outbound direction, respectively | 51 | | radarName | Radar sensor that captured the data. This field is used to associate with cameras and other radars. | TEXT | | 52 | | deployment\_id | Each ID represents a unique deployment configuration and/or location for the device. This acts a foreign key link to the \`deployment\` table, \`id\` column. | TEXT, FOREIGN KEY | \`deployment\`.\`id\` foreign key, may be null | 53 | 54 | ### radar\_timed\_speed\_counts table 55 | 56 | | Attribute | Description | SQLite data type | Valid values | 57 | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------- | 58 | | time | Unix timestamp directly from radar | REAL | Unix timestamp in Seconds | 59 | | direction | Object moving direction relative to radar placement; i.e. "outbound" is moving away from radar. | TEXT | "outbound" or "inbound" | 60 | | units | Unit of measure for velocity/speed set on radar (configurable) | TEXT | "mph" | 61 | | count | Number of object detection (DOV) measurements; should correspond to number of objects/vehicles that passed through radar zone | INTEGER | Integer, positive | 62 | | average | Average speed/velocity across all object detections (count) during measurement interval; defined by \`units\` attribute | REAL | Integer, Positive, Negative corresponding to inbound and outbound direction, respectively | 63 | | radarName | Radar sensor that captured the data. This field is used to associate with cameras and other radars. | TEXT | | 64 | | deployment\_id | Each ID represents a unique deployment configuration and/or location for the device. This acts a foreign key link to the \`deployment\` table, \`id\` column. | TEXT, FOREIGN KEY | \`deployment\`.\`id\` foreign key, may be null | 65 | 66 | ### radar\_raw\_speed\_magnitude table 67 | 68 | | Attribute | Description | SQLite data type | Valid values | 69 | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | -------------------------------------------------------------------------------- | 70 | | time | Unix timestamp directly from radar | REAL | Unix timestamp in Seconds | 71 | | unit | Unit of measure for velocity/speed set on radar (configurable) | TEXT | "mph" | 72 | | magnitude | Array of individual magnitude measurements for the sampling time depending on K+ setting; e.g. one speed represents \~50-ms at 20k samples. Corresponds to speed's array location in descending order of speed (configurable). | TEXT | Positive | 73 | | speed | Array of individual Speed/velocity for the sampling time depending on K+ setting; e.g. one speed represents \~50-ms at 20k samples. Corresponds to magnitude's array location in descending order of speed (configurable). | REAL | Positive, Negative corresponding to inbound and outbound direction, respectively | 74 | | radarName | Radar sensor that captured the data. This field is used to associate with cameras and other radars. | TEXT | | 75 | | deployment\_id | Each ID represents a unique deployment configuration and/or location for the device. This acts a foreign key link to the \`deployment\` table, \`id\` column. | TEXT, FOREIGN KEY | \`deployment\`.\`id\` foreign key, may be null | 76 | 77 | ### radar\_raw\_speed\_magnitude\_single table 78 | 79 | | Attribute | Description | SQLite data type | Valid values | 80 | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | -------------------------------------------------------------------------------- | 81 | | time | Unix timestamp directly from radar | REAL | Unix timestamp in Seconds | 82 | | unit | Unit of measure for velocity/speed set on radar (configurable) | TEXT | "mph" | 83 | | magnitude | Array of individual magnitude measurements for the sampling time depending on K+ setting; e.g. one speed represents \~50-ms at 20k samples. Corresponds to speed's array index==0 in descending order of speed (configurable). | REAL | Positive | 84 | | speed | Array of individual Speed/velocity for the sampling time depending on K+ setting; e.g. one speed represents \~50-ms at 20k samples. Corresponds to magnitude's array index==0 in descending order of speed (configurable). | REAL | Positive, Negative corresponding to inbound and outbound direction, respectively | 85 | | radarName | Radar sensor that captured the data. This field is used to associate with cameras and other radars. | TEXT | | 86 | | deployment\_id | Each ID represents a unique deployment configuration and/or location for the device. This acts a foreign key link to the \`deployment\` table, \`id\` column. | TEXT, FOREIGN KEY | \`deployment\`.\`id\` foreign key, may be null | 87 | 88 | ### radar\_oc\_payload table 89 | 90 | | Attribute | Description | SQLite data type | Valid values | 91 | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------- | 92 | | start\_time | Unix timestamp directly from radar | REAL | Unix timestamp in Seconds | 93 | | end\_time | Unix timestamp directly from radar | REAL | Unix timestamp in Seconds | 94 | | delta\_time\_msec | Different in end and start times in milliseconds, representing the amount of time the object was in the radar zone: i.e. \`end\_time\` minus \`start\_time\` \* 1000 | REAL | | 95 | | direction | Object moving direction relative to radar placement; i.e. "outbound" is moving away from radar. | TEXT | inbound, outbound | 96 | | frames\_count | Number of frames, defined by doppler pings (equivalent to OS) for an object through the field of view (FOV) | INTEGER | | 97 | | velocity\_max | Maximum velocity/speed of object through field of view (FOV) | REAL | | 98 | | velocity\_min | Minimum velocity/speed of object through field of view (FOV) | REAL | | 99 | | magnitude\_max | Maximium magnitude of doppler radar response of object through field of view (FOV) | REAL | | 100 | | magnitude\_mean | Average / mean magnitude of doppler radar response of object through field of view (FOV) | REAL | | 101 | | velocity\_change | Delta max\_speed – min\_speed. This can help with indication of the lane a vehicle is in. A vehicle farther to the edge of the FoV will have a higher cosine error change and therefore delta speed. This should be normalized to speed so offline we’ve used (max\_speed – min\_speed)/max\_speed. A lower number tends to show a vehicle in the farther lane over. | REAL | | 102 | | frames\_per\_velocity | Number of frames captured per unit of velocity. This acts as the inverse to speed in order to calculate length of object. Calculated as \`frames\_count / velocity\_max\` | REAL | | 103 | | object\_length | Estimted length of object, calculated by taking speed and time through field of view. | REAL | | 104 | | units | Velocity unit of measurement. Will also match length units, relatively. | TEXT | mph, mps - for Miles Per Hour (imperial) and Meters Per Second (metric), respectively | 105 | | object\_label | RESERVE FOR FUTURE USE. Radar-based classification for the type of object that moved through the field of view. This is estimated from common roadway-based objects. Payload is a JSON object containing key:value of the top -calculated labels and respective likelihood score. | TEXT, JSON | JSON object with key:value pairs; e.g. {"car": 0.86, "bike": 0.25, "person": 0.10} | 106 | | radarName | Radar sensor that captured the data. This field is used to associate with cameras and other radars. | TEXT | | 107 | | deployment\_id | Each ID represents a unique deployment configuration and/or location for the device. This acts a foreign key link to the \`deployment\` table, \`id\` column. | TEXT, FOREIGN KEY | \`deployment\`.\`id\` foreign key, may be null | 108 | -------------------------------------------------------------------------------- /docs/setup-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: sliders 3 | description: Steps to connect to and setup your Traffic Monitor. 4 | --- 5 | 6 | # Setup Guide 7 | 8 | At this point you have [deployed](deployment-and-mounting-guide.md) your traffic monitor and it is running. Nice job! 9 | 10 | This guide will walk you though configuring your device based on your sensors (required), adjust it for roadway conditions (recommended), optimize your data capture, and connect with the ThingsBoard platform (optional). 11 | 12 | ## Steps 13 | 14 | {% hint style="warning" %} 15 | The default configuration files have disabled all sensors until you follow these steps. There will be no data captured until you enable your sensors using the following steps. 16 | {% endhint %} 17 | 18 | * [x] [#connect-to-your-device](setup-guide.md#connect-to-your-device "mention") 19 | * [x] [#configure-frigate-zones](setup-guide.md#configure-frigate-zones "mention") 20 | * [x] [#configure-node-red](setup-guide.md#configure-node-red "mention") 21 | 22 | ## Connect to your Device 23 | 24 | ### Physical Access 25 | 26 | Physical access to the device is a less-convenient method but will allow the most control to address issues. 27 | 28 | #### Monitor, Keyboard, Mouse 29 | 30 | See [Raspberry Pi Getting Started](https://www.raspberrypi.com/documentation/computers/getting-started.html#display) for more information on connecting your Raspberry Pi. It should be as simple as plugging in a USB keyboard, USB mouse, and micro HDMI cable to your monitor. In this case, you can use `localhost` as the RPi IP address or use the host name. 31 | 32 | #### SD Card 33 | 34 | If your Traffic Monitor uses the default Raspberry Pi installation method, you will have an [SD Card boot media](https://www.raspberrypi.com/documentation/computers/getting-started.html#sd-cards) that contains all your system files. If necessary, you can insert the card into a micro-SD card reader to access the entire Raspberry Pi OS directory structure. 35 | 36 | ### Remote Access 37 | 38 | Remote access allows you to control various parts of your Raspberry Pi without connecting it to a monitor, keyboard, or mouse. This must be done from another computer, e.g. a laptop. See [Raspberry Pi's remote access](https://www.raspberrypi.com/documentation/computers/remote-access.html#introduction-to-remote-access) docs for a full rundown of options. 39 | 40 | You will need to know the Traffic Monitor / Raspberry Pi IP address or host name to connect to the various configuration environments. 41 | 42 | #### Finding Your IP address 43 | 44 | * If you chose the [build-your-own-device-diy](build-your-own-device-diy/ "mention") route, we recommend you set up WiFi credentials by following the [Raspberry Pi Imager docs](https://www.raspberrypi.com/documentation/computers/getting-started.html#installing-the-operating-system) and it will automatically be accessible the network you specified. Find the RPi IP address via your router, or if your router supports DNS host names, you can use the host name set on the RPi. 45 | * If you received a **pre-built device**, check with your provider for specific instructions. To get you started, it may be available as a Hotspot that will [host a wireless network](https://www.raspberrypi.com/documentation/computers/configuration.html#host-a-wireless-network-from-your-raspberry-pi). Connect to it like any WiFi network, look for the host name as the SSID. The IP address of the Raspberry Pi will be the Gateway IP address or you may use the host name set on the RPi. 46 | 47 | See [Find the IP address of your Raspberry Pi](https://www.raspberrypi.com/documentation/computers/remote-access.html#ip-address) for more options. 48 | 49 | ## Configure Frigate Zones 50 | 51 | Frigate controls and generates object detection events with the camera. 52 | 53 | {% hint style="info" %} 54 | This section describes setting up Frigate with the Traffic Monitor [recommended-hardware.md](recommended-hardware.md "mention"). If you have alternative or optional camera(s) or other components, you may need additional configuration. Reference the official [Frigate Configuration](https://docs.frigate.video/guides/getting_started#configuring-frigate) for more details. 55 | {% endhint %} 56 | 57 | Frigate has a well-developed front-end user interface that can be accessed by visiting `http://:5000` in a browser. 58 | 59 | The Traffic Monitor will be expecting the following specifically named [Frigate zones](https://docs.frigate.video/configuration/zones/) to work properly with all dashboards and workflow logic. These need to manually drawn based on your deployment. 60 | 61 | {% hint style="success" %} 62 | Ensure following [Frigate zones](https://docs.frigate.video/configuration/zones/) are manually configured each time a the traffic monitor is re-positioned or relocated, based on your unique deployment and roadway conditions. 63 | {% endhint %} 64 | 65 | Set up or modify the following zones, overlaying any temporary or permanent stationary objects: 66 | 67 | 1. zone\_capture - Set to capture the entire roadway, including sidewalks that are clearly in view for counting objects. 68 | 2. zone\_near - Paired with `zone_near`, this will determine if an object moves "outbound" or "inbound". Set this to be roughly the further half of the `zone_capture` region. 69 | 3. zone\_far - Paired with `zone_far`, this will determine if an object moves "outbound" or "inbound". Set this to be roughly the closer half of the `zone_capture` region. 70 | 4. zone\_radar - (for units equipped with radar) - This should correspond to the field of view for the radar (where it can pick up accurate measurements) on the street. It will roughly make a rectangle in the center of the camera field of view from curb to curb. 71 | 72 |

Properly configured Frigate Zones

73 | 74 | After changes are made, you will need to restart Frigate before they take effect. You can do this via **Frigate > Settings > Restart Frigate**. 75 | 76 | ## Configure Node-RED 77 | 78 | Node-RED controls most of the workflow logic and data collection. 79 | 80 | You will need to [#connect-to-your-device](setup-guide.md#connect-to-your-device "mention") to edit the [node-red-config.md](configuration/node-red-config.md "mention") files. 81 | 82 | 1. Open up the terminal or via SSH enter the command: `nano ~/.node-red/config.yml` to begin editing the config file. 83 | 2. Change the deployment location information to represent the current deployment. Get your latitude and longitude from any map service, such as Google Maps and enter bearing with the single-letter cardinal direction the traffic monitor is facing. 84 | 85 | ```yaml 86 | deployment: 87 | lat: 45.5225 88 | lon: -122.6919 89 | bearing: n 90 | ``` 91 | 92 | 3. Modify sensors to reflect currently installed components. For example, with a single Raspberry Pi Camera and Radar, it may look like this: 93 | 94 | ```yaml 95 | sensors: 96 | cameras: 97 | picam_h264: 98 | enabled: true 99 | camera_radar: TM_RADAR_SERIAL_PORT_00 100 | radars: 101 | TM_RADAR_SERIAL_PORT_00: 102 | enabled: true 103 | ``` 104 | 105 | 4. To save changes, press Ctr+o (hold control and o) 106 | 5. To exit, press Ctr+x (hold control and x) 107 | 108 | You will need to restart Node-RED for setting to take effect. Do this by entering the command `systemctl restart nodered` into the terminal. 109 | -------------------------------------------------------------------------------- /node-red-tm/Dockerfile.j2: -------------------------------------------------------------------------------- 1 | FROM nodered/node-red:{{ tmsetup_node_red_tm_version }} 2 | WORKDIR /usr/src/node-red 3 | COPY data/package.json package.json 4 | RUN npm install --unsafe-perm --no-update-notifier --no-fund --only=production 5 | -------------------------------------------------------------------------------- /node-red-tm/config/config.yml.j2: -------------------------------------------------------------------------------- 1 | thingsboard: 2 | enabled: false 3 | host: {{ tmsetup_thingsboard_server }} 4 | protocol: {{ tmsetup_thingsboard_protocol }} 5 | port: {{ tmsetup_thingsboard_port }} 6 | access_token: {{ tmsetup_thingsboard_api_key }} 7 | 8 | deployment: 9 | lat: {{ tmsetup_latitude }} 10 | lon: {{ tmsetup_longitude }} 11 | bearing: {{ tmsetup_bearing }} 12 | 13 | sensors: 14 | cameras: 15 | picam_h264: 16 | enabled: false 17 | camera_radar: TM_RADAR_SERIAL_PORT_00 18 | 19 | radars: 20 | TM_RADAR_SERIAL_PORT_00: 21 | enabled: false 22 | 23 | airquality_monitors: 24 | sensorname01: 25 | enabled: false 26 | mqtt_topic_incoming: readings 27 | 28 | time: 29 | timezone: {{ tmsetup_timezone }} 30 | npt_set: true 31 | -------------------------------------------------------------------------------- /node-red-tm/data/README.md: -------------------------------------------------------------------------------- 1 | traffic-monitor 2 | ============== 3 | 4 | Traffic monitoring solution node-red flow 5 | 6 | ### About 7 | 8 | Traffic Monitor built with edge ML for object detection and radar for speed monitoring. -------------------------------------------------------------------------------- /node-red-tm/data/flows_cred.json: -------------------------------------------------------------------------------- 1 | { 2 | "$": "847dc097b39bd03cb8777826fe6c10ded1Fh6wjqbQL0YTb+T/oN7MxmSew81UdK70w3KhmXzUVkI4H/3phM47N6Gu+0rOPxzKYUray6M3sM5YZUZ4oXw7hGnYLI255T5jvg6Xz38AGKLcfSVrCop9Uy7wgYMWHFaCZEVY1qlJLDHLacri+M/Enk9aF2oV6rRsy26bqnzh/WQPlTtJK1D466fP9g3UY7HcBKOOmEBdzKCgKiHCtNO6M3/sxv2JxAVw0sY/xM5QvpwDGctpNrfsjDGFI2nNzIz6+bBzc+yZd5kl7/pnLcsyj7jq2KOjnnFkYUzjEOJi3r4JxW4Ej8y0JuC9yVJSntGijzqmilr1y2GKsL" 3 | } -------------------------------------------------------------------------------- /node-red-tm/data/package.json.j2: -------------------------------------------------------------------------------- 1 | { 2 | "name": "traffic-monitor", 3 | "description": "Traffic Monitor built with edge ML for object detection and radar for speed monitoring", 4 | "version": "{{ tmsetup_tm_version }}", 5 | "dependencies": { 6 | "node-red": "{{ tmsetup_node_red_tm_version }}", 7 | "node-red-contrib-aedes": "~0.13.0", 8 | "node-red-contrib-image-output": "~0.6.4", 9 | "node-red-dashboard": "~3.6.5", 10 | "node-red-node-serialport": "~2.0.2", 11 | "node-red-node-sqlite": "1.1.0", 12 | "node-red-node-base64": "1.0.0", 13 | "node-red-contrib-os": "0.2.1" 14 | }, 15 | "node-red": { 16 | "settings": { 17 | "flowFile": "/data/flows.json", 18 | "credentialsFile": "/data/flows_cred.json" 19 | } 20 | }, 21 | "scripts": { 22 | "start": "node $NODE_OPTIONS node_modules/node-red/red.js $FLOWS", 23 | "debug": "node --inspect=0.0.0.0:9229 $NODE_OPTIONS node_modules/node-red/red.js $FLOWS", 24 | "debug_brk": "node --inspect=0.0.0.0:9229 --inspect-brk $NODE_OPTIONS node_modules/node-red/red.js $FLOWS" 25 | } 26 | } -------------------------------------------------------------------------------- /node-red-tm/data/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node-RED Settings, refer to documentation: 3 | * https://nodered.org/docs/user-guide/runtime/settings-file 4 | **/ 5 | 6 | module.exports = { 7 | flowFile: "flows.json", 8 | credentialSecret: process.env.NODE_RED_CREDENTIAL_SECRET, 9 | flowFilePretty: true, 10 | 11 | adminAuth: { 12 | type: "credentials", 13 | users: [ 14 | { 15 | username: "admin", 16 | password: 17 | "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", 18 | permissions: "*", 19 | }, 20 | ], 21 | }, 22 | 23 | uiPort: process.env.PORT || 1880, 24 | 25 | diagnostics: { 26 | enabled: true, 27 | ui: true, 28 | }, 29 | runtimeState: { 30 | enabled: false, 31 | ui: false, 32 | }, 33 | 34 | logging: { 35 | console: { 36 | level: "info", 37 | metrics: false, 38 | audit: false, 39 | }, 40 | }, 41 | 42 | contextStorage: { 43 | default: { 44 | module:"memory", 45 | } 46 | }, 47 | 48 | exportGlobalContextKeys: false, 49 | 50 | externalModules: { 51 | }, 52 | 53 | editorTheme: { 54 | palette: { 55 | }, 56 | projects: { 57 | enabled: true, 58 | workflow: { 59 | mode: "manual", 60 | }, 61 | }, 62 | 63 | codeEditor: { 64 | lib: "monaco", 65 | options: { 66 | }, 67 | }, 68 | markdownEditor: { 69 | mermaid: { 70 | enabled: true, 71 | }, 72 | }, 73 | 74 | multiplayer: { 75 | enabled: false, 76 | }, 77 | }, 78 | 79 | functionExternalModules: true, 80 | 81 | functionTimeout: 0, 82 | 83 | functionGlobalContext: { 84 | }, 85 | 86 | debugMaxLength: 1000, 87 | 88 | mqttReconnectTime: 15000, 89 | 90 | serialReconnectTime: 15000, 91 | 92 | }; 93 | -------------------------------------------------------------------------------- /node-red-tm/docker-compose-node-red-tm.yaml.j2: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Node-RED Stack or Compose 3 | ################################################################################ 4 | # docker stack deploy node-red --compose-file docker-compose-node-red.yml 5 | # docker-compose -f docker-compose-node-red.yml -p myNoderedProject up 6 | # for OPS-243 device, see 7 | # https://nodered.org/docs/getting-started/docker#serial-port---dialout---adding-groups 8 | # 9 | # disable volumes for data when deploying to allow image to be built with all packages 10 | ################################################################################ 11 | services: 12 | node-red: 13 | container_name: node-red-tm 14 | restart: unless-stopped 15 | build: . 16 | ports: 17 | - "1880:1880" #node-red flow gui and dashboard ui port 18 | - "1883:1883" #aedes mqtt broker port 19 | volumes: 20 | - /etc/localtime:/etc/localtime:ro 21 | - {{ tmsetup_codedir }}/node-red-tm/config:/config 22 | - {{ tmsetup_codedir }}/node-red-tm/data:/data 23 | - {{ tmsetup_codedir }}/node-red-tm/db:/db 24 | {% if tmsetup_radars_confirmed is defined and tmsetup_radars_confirmed is iterable %} 25 | devices: 26 | {% for rad in tmsetup_radars_confirmed %} 27 | - "{{ rad }}" 28 | {% endfor %} 29 | {% endif %} 30 | group_add: 31 | - dialout 32 | - "{{ tmsetup_codeowner_gid }}" 33 | user: "{{ tmsetup_codeowner_uid }}" 34 | env_file: "node-red-tm.env" 35 | depends_on: 36 | frigate: 37 | condition: service_healthy 38 | -------------------------------------------------------------------------------- /node-red-tm/node-red-tm: -------------------------------------------------------------------------------- 1 | node-red-tm -------------------------------------------------------------------------------- /node-red-tm/node-red-tm.env.j2: -------------------------------------------------------------------------------- 1 | {% raw %} 2 | ######## 3 | # This file contains node-red environment variables loaded by node-red.service 4 | # Read more at https://nodered.org/docs/user-guide/environment-variables 5 | # and https://fedoraproject.org/wiki/Packaging:Systemd 6 | # 7 | # This file shall be located at the root node-red directory, usually `~/.node-red` 8 | # this file is loaded by `systemd`, changes can be applied 9 | # by running the command `node-red-restart` in the terminal 10 | # read more at https://nodered.org/docs/getting-started/raspberrypi 11 | # 12 | # Uses: 13 | # - variables can be used in settings.js by calling `process.env.ENV_VAR` 14 | # - node property can be set by calling `${ENV_VAR} 15 | # 16 | ######## 17 | 18 | # traffic monitor open source software release version 19 | {% endraw %} 20 | TM_VERSION='{{ tmsetup_tm_version }}' 21 | TM_RPI_SN='{{ tmsetup_raspberry_pi_serial }}' 22 | TM_HOSTNAME= '{{ ansible_hostname }}' 23 | 24 | # used in settings.js for credentialSecret 25 | NODE_RED_CREDENTIAL_SECRET='myNodeRED1234' 26 | 27 | # database locations, relative to user directory defined in settings.js 28 | # will be relative path to store SQLite databases 29 | TM_DATABASE_PATH_TMDB='/db/tmdb.sqlite' 30 | 31 | # mqtt broker for incoming Frigate events 32 | # Settings below set up the aedes broker node 33 | TM_MQTT_BROKER_HOST='localhost' 34 | TM_MQTT_BROKER_PORT='1883' 35 | # mqtt user, leave blank for no authentication 36 | TM_MQTT_BROKER_USERNAME='' 37 | # mqtt password, leave blank for no authentication 38 | TM_MQTT_BROKER_PASSWORD='' 39 | 40 | # defines system USB serial port for radar 41 | # run `ls -lat /sys/class/tty/ttyACM*` to list devices 42 | TM_RADAR_SERIAL_PORT_00='/dev/ttyACM0' 43 | TM_RADAR_SERIAL_PORT_01='/dev/ttyACM1' 44 | TM_RADAR_SERIAL_PORT_02='/dev/ttyACM2' 45 | TM_RADAR_SERIAL_PORT_03='/dev/ttyACM3' -------------------------------------------------------------------------------- /script/ansible/localhost: -------------------------------------------------------------------------------- 1 | localhost -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/README.md: -------------------------------------------------------------------------------- 1 | tmsetup 2 | ========= 3 | 4 | Local role for installation of Traffic Monitor software stack on Raspbery Pi device 5 | 6 | Requirements 7 | ------------ 8 | 9 | Tested on versions. May work on lower versions but this is earliest tested. 10 | 11 | - ansble_version: `>=2.17.8` 12 | - python_version: `>=3.10.12` 13 | 14 | - ansible_collections: `community.general` 15 | 16 | Role Variables 17 | -------------- 18 | 19 | - `tmsetup_codeowner`: STRING # The user that non-system services will run under and all data will be owned by 20 | - default: `'{{ ansible_user_id }}'` 21 | - `tmsetup_codedir`: STRING # Path to directory for frigate and other application data will be stored 22 | - default: `'{{ ansible_user_dir }}/code'` 23 | - `tmsetup_nodereddir`: STRING # Path to directory for nodered installation 24 | - default: `'{{ ansible_user_dir }}/.node-red'` 25 | - `tmsetup_timezone`: STRING - Timezone to set system clock to. You can view a list of availbe timezones by running `timedatectl list-timezones` 26 | - default: `'America/Los_Angeles'` 27 | - `tmsetup_frigate_rtsp_password`: STRING # Password for frigate rtsp for frigate container 28 | - default: `password` 29 | - `tmsetup_frigate_mqtt_user`: STRING # MQTT user for Frigate container 30 | - default: `mqtt_user` 31 | - `tmsetup_frigate_mqtt_password`: STRING # MQTT user password for frigate container 32 | - default: `mqtt_pass` 33 | - `tmsetup_frigate_tpu_device`: STRING # Coral TPU Device. Currently supported either 'pci' or 'usb' 34 | - default: `pci` 35 | 36 | Example Playbook 37 | ---------------- 38 | 39 | ```yml 40 | --- 41 | - name: Traffic Monitr Setup 42 | hosts: localhost 43 | tasks: 44 | - name: Run tmsetup role 45 | ansible.builtin.import_role: 46 | name: tmsetup 47 | ... 48 | ``` 49 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup Default Variables (user configurable) 3 | tmsetup_codeowner: 'tmadmin' 4 | tmsetup_codegroup: '{{ tmsetup_codeowner }}' 5 | tmsetup_codeowner_homedir: None 6 | tmsetup_codedir: '/opt/traffic-monitor' 7 | tmsetup_timezone: 'America/Los_Angeles' 8 | 9 | tmsetup_frigate_rtsp_password: password 10 | tmsetup_frigate_mqtt_user: mqtt_user 11 | tmsetup_frigate_mqtt_password: mqtt_pass 12 | tmsetup_frigate_tpu_device: pci 13 | 14 | tmsetup_radar_paths: 15 | - /dev/ttyACM0 16 | - /dev/ttyACM1 17 | - /dev/ttyACM2 18 | - /dev/ttyACM3 19 | 20 | tmsetup_pcie_tpu_paths: 21 | - /dev/apex_0 22 | 23 | tmsetup_thingsboard_server: 'tb.server.com' 24 | tmsetup_thingsboard_protocol: 'https' 25 | tmsetup_thingsboard_port: '443' 26 | tmsetup_thingsboard_api_key: '' 27 | 28 | tmsetup_latitude: '' 29 | tmsetup_longitude: '' 30 | tmsetup_bearing: '' 31 | 32 | tmsetup_force_configs: false 33 | tmsetup_reboot_touch_file: ~/tm-reboot-required 34 | 35 | ... 36 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/files/65-apex.rules: -------------------------------------------------------------------------------- 1 | SUBSYSTEM=="apex", MODE="0660", GROUP="apex" -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/files/gasket-builder.Dockerfile: -------------------------------------------------------------------------------- 1 | # Thank you to jnicolson for providing this Dockerfile to fix Gasket-DKMS build issues 2 | # https://github.com/jnicolson/gasket-builder 3 | 4 | FROM ubuntu:24.04 AS build-stage 5 | 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | RUN apt-get update 8 | RUN apt-get install -q -y --no-install-recommends git curl devscripts dkms dh-dkms build-essential debhelper 9 | 10 | RUN git clone https://github.com/google/gasket-driver.git 11 | RUN cd gasket-driver && curl -L https://github.com/heitbaum/gasket-driver/commit/4b2a1464f3b619daaf0f6c664c954a42c4b7ce00.patch | git apply -v 12 | RUN cd gasket-driver && debuild -us -uc -tc -b 13 | 14 | FROM scratch AS export-stage 15 | COPY --from=build-stage *.deb . 16 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/files/go2rtc.yaml: -------------------------------------------------------------------------------- 1 | # /var/lib/go2rtc/go2rtc.yaml 2 | streams: 3 | picam_h264: exec:rpicam-vid --camera 0 --mode 2304:1296 --framerate 15 --exposure sport --hdr --timeout 0 --nopreview --codec h264 --libav-video-codec h264 --libav-format h264 --inline -o - 4 | picam_gs_h264: exec:rpicam-vid --camera 1 --mode 1456:1088 --framerate 15 --exposure sport --hdr --timeout 0 --nopreview --codec h264 --libav-video-codec h264 --libav-format h264 --inline -o - -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/files/node-red-tm: -------------------------------------------------------------------------------- 1 | ../../../../../node-red-tm/ -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/files/utils: -------------------------------------------------------------------------------- 1 | ../../../../../utils/ -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/files/wlan_check.sh: -------------------------------------------------------------------------------- 1 | ../../../../../utils/wlan_check.sh -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/files/z-wifi-powersave-off.conf: -------------------------------------------------------------------------------- 1 | # file name starting with 'z' chosen to make sure this overwrites any 2 | # distribution default configuration 3 | 4 | # Turn off wifi power management because it seems to cause failures 5 | [connection] 6 | wifi.powersave = 2 -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tmsetup handlers 3 | - name: Restart go2rtc_server service 4 | become: true 5 | ansible.builtin.systemd_service: 6 | daemon_reload: true 7 | name: go2rtc_server.service 8 | state: restarted 9 | 10 | - name: Build container images 11 | become: true 12 | community.docker.docker_compose_v2: 13 | build: always 14 | files: 15 | - compose.yaml 16 | project_src: "{{ tmsetup_codedir }}" 17 | recreate: always 18 | state: stopped 19 | 20 | - name: Restart NetworkManager service 21 | become: true 22 | ansible.builtin.systemd_service: 23 | daemon_reload: true 24 | name: NetworkManager.service 25 | state: restarted 26 | 27 | - name: Restart tm-docker service 28 | become: true 29 | ansible.builtin.systemd_service: 30 | daemon_reload: true 31 | name: tm-docker.service 32 | state: restarted 33 | 34 | - name: TMSetup - Flag for reboot 35 | delegate_to: localhost 36 | ansible.builtin.copy: 37 | dest: "{{ tmsetup_reboot_touch_file }}" 38 | content: '1' 39 | mode: '0644' 40 | ... 41 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/base_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup - Base Setup tasks 3 | - name: TMSetup - Base Setup - Add TM user to sudo group 4 | become: true 5 | ansible.builtin.user: 6 | append: true 7 | groups: sudo 8 | name: '{{ tmsetup_codeowner }}' 9 | 10 | - name: TMSetup - Base Setup - Check if reboot needed 11 | ansible.builtin.stat: 12 | path: /var/run/reboot-required 13 | register: tmsetup_reboot_required_stat_register 14 | changed_when: tmsetup_reboot_required_stat_register.stat.exists 15 | notify: TMSetup - Flag for reboot 16 | 17 | - name: TMSetup - Base Setup - Create code owner 18 | become: true 19 | ansible.builtin.user: 20 | append: true 21 | groups: '{{ tmsetup_codegroup }}' 22 | name: '{{ tmsetup_codeowner }}' 23 | state: present 24 | register: tmsetup_base_register_user_codeowner 25 | 26 | - name: TMSetup - Base Setup - Set var for codeowner home directory 27 | ansible.builtin.set_fact: 28 | tmsetup_codeowner_homedir: '{{ tmsetup_base_register_user_codeowner.home }}' 29 | tmsetup_codeowner_uid: '{{ tmsetup_base_register_user_codeowner.uid }}' 30 | tmsetup_codeowner_gid: '{{ tmsetup_base_register_user_codeowner.group }}' 31 | 32 | 33 | - name: TMSetup - Base Setup - Set timezone 34 | become: true 35 | community.general.timezone: 36 | name: '{{ tmsetup_timezone }}' 37 | 38 | - name: TMSetup - Base Setup - Create Directories 39 | become: true 40 | loop: 41 | - '{{ tmsetup_codedir }}' 42 | - '{{ tmsetup_codedir }}/services' 43 | - '{{ tmsetup_codedir }}/utils' 44 | ansible.builtin.file: 45 | group: '{{ tmsetup_codegroup }}' 46 | mode: '0750' 47 | owner: '{{ tmsetup_codeowner }}' 48 | path: '{{ item }}' 49 | state: directory 50 | 51 | - name: TMSetup - Base Setup - Remove conflicting packages 52 | become: true 53 | ansible.builtin.apt: 54 | autoremove: true 55 | name: '{{ tmsetup_remove_packages }}' 56 | purge: true 57 | state: absent 58 | 59 | - name: TMSetup - Base Setup - Install required packages 60 | become: true 61 | ansible.builtin.apt: 62 | name: '{{ tmsetup_packages }}' 63 | state: present 64 | update_cache: false 65 | 66 | - name: TMSetup - Base Setup - Copy utils files to codedir 67 | become: true 68 | ansible.builtin.copy: 69 | src: utils/ 70 | dest: "{{ tmsetup_codedir }}/utils/" 71 | owner: "{{ tmsetup_codeowner }}" 72 | group: "{{ tmsetup_codegroup }}" 73 | mode: "0755" 74 | 75 | ... 76 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/coraltpu_pci_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This set up of tasks is still a work in progress - there are severe issues with 6.12+ kernel versions and the gasket-dkms driver 3 | # Setup tasks for PCIe or M.2 Coral TPU devices. 4 | 5 | - name: TMSetup - Coral TPU PCI Setup - Enable PCIe connector at bootup 6 | become: true 7 | ansible.builtin.blockinfile: 8 | path: /boot/firmware/config.txt 9 | block: | 10 | # Enable the PCIe External connector. 11 | dtparam=pciex1 12 | kernel=kernel8.img 13 | # Enable Pineboards Hat Ai 14 | dtoverlay=pineboards-hat-ai 15 | append_newline: true 16 | prepend_newline: true 17 | create: true 18 | owner: root 19 | group: root 20 | mode: '0644' 21 | state: present 22 | notify: TMSetup - Flag for reboot 23 | 24 | - name: TMSetup - Corap TPU PCI setup - Get package facts 25 | ansible.builtin.package_facts: 26 | 27 | - name: TMSetup - Coral TPU PCI Setup - Build and install gasket-dkms if not already installed 28 | when: ansible_facts['packages']['gasket-dkms'] is not defined 29 | block: 30 | - name: TMSetup - Coral TPU PCI Setup - Copy gasket-build.Dockerfile 31 | become: true 32 | ansible.builtin.copy: 33 | src: gasket-builder.Dockerfile 34 | dest: /tmp/gasket-builder.Dockerfile 35 | mode: '0644' 36 | 37 | - name: TMSetup - Coral TPU PCI Setup - Run Docker build 38 | become: true 39 | ansible.builtin.command: 40 | chdir: /tmp 41 | cmd: docker build -f gasket-builder.Dockerfile --output . . 42 | creates: /tmp/gasket-dkms_1.0-18_all.deb 43 | 44 | - name: TMSetup - Coral TPU PCI Setup - Install gasket-dkms from .deb 45 | become: true 46 | ansible.builtin.apt: 47 | deb: /tmp/gasket-dkms_1.0-18_all.deb 48 | state: present 49 | notify: TMSetup - Flag for reboot 50 | 51 | - name: TMSetup - Coral TPU PCI Setup - Create apex udev rules to manage device permissions 52 | become: true 53 | ansible.builtin.copy: 54 | src: 65-apex.rules 55 | dest: /etc/udev/rules.d/65-apex.rules 56 | owner: root 57 | group: root 58 | mode: '0644' 59 | notify: TMSetup - Flag for reboot 60 | 61 | - name: TMSetup - Coral TPU PCI Setup - Create apex group 62 | become: true 63 | ansible.builtin.group: 64 | name: apex 65 | state: present 66 | 67 | - name: TMSetup - Coral TPU PCI Setup - Add codeowner to apex group 68 | become: true 69 | ansible.builtin.user: 70 | name: '{{ tmsetup_codeowner }}' 71 | groups: apex 72 | append: true 73 | 74 | ... 75 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/coraltpu_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup - Coral TPU Setup tasks 3 | - name: TMSetup - Coral TPU Setup - Add Coral-TPU Repository apt key 4 | become: true 5 | ansible.builtin.apt_key: 6 | url: https://packages.cloud.google.com/apt/doc/apt-key.gpg 7 | state: present 8 | 9 | - name: TMSetup - Coral TPU Setup - Add Coral-TPU apt repo 10 | become: true 11 | ansible.builtin.apt_repository: 12 | filename: coral-edgetpu 13 | repo: 'deb https://packages.cloud.google.com/apt coral-edgetpu-stable main' 14 | state: present 15 | 16 | - name: TMSetup - Coral TPU Setup - Install runtime library 17 | become: true 18 | ansible.builtin.apt: 19 | update_cache: true 20 | name: libedgetpu1-std 21 | state: present 22 | 23 | ... 24 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/docker_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup - Docker Setup tasks 3 | - name: TMSetup - Docker Setup - Create apt key directory 4 | become: true 5 | ansible.builtin.file: 6 | group: root 7 | mode: '0755' 8 | owner: root 9 | path: /etc/apt/keyring 10 | state: directory 11 | 12 | - name: TMSetup - Docker Setup - Download docker key repo key 13 | become: true 14 | ansible.builtin.get_url: 15 | dest: /etc/apt/keyrings/docker.asc 16 | group: root 17 | mode: '0644' 18 | owner: root 19 | url: "https://download.docker.com/linux/{{ ansible_os_family | lower }}/gpg" 20 | 21 | - name: TMSetup - Docker Setup - Create Docker apt repo file 22 | become: true 23 | ansible.builtin.apt_repository: 24 | filename: docker 25 | repo: >- 26 | deb [arch={{ tmsetup_dpkg_architecture }} signed-by=/etc/apt/keyrings/docker.asc] 27 | https://download.docker.com/linux/{{ ansible_os_family | lower }} {{ ansible_distribution_release }} stable 28 | state: present 29 | 30 | - name: TMSetup - Docker Setup - Install Docker CE packages 31 | become: true 32 | ansible.builtin.apt: 33 | name: 34 | - containerd.io 35 | - docker-buildx-plugin 36 | - docker-ce 37 | - docker-ce-cli 38 | - docker-compose-plugin 39 | state: present 40 | notify: Restart tm-docker service 41 | 42 | - name: TMSetup - Docker Setup - Add TM user to docker group 43 | become: true 44 | ansible.builtin.user: 45 | append: true 46 | groups: docker 47 | name: '{{ tmsetup_codeowner }}' 48 | 49 | - name: TMSetup - Docker Setup - Copy compose.yaml to code directories 50 | become: true 51 | ansible.builtin.template: 52 | dest: '{{ tmsetup_codedir }}/compose.yaml' 53 | force: true 54 | group: '{{ tmsetup_codegroup }}' 55 | mode: '0640' 56 | owner: '{{ tmsetup_codeowner }}' 57 | src: compose.yaml.j2 58 | notify: 59 | - Build container images 60 | - Restart tm-docker service 61 | 62 | - name: TMSetup - Docker Setup - Create Systemd unit file for tm-docker 63 | become: true 64 | ansible.builtin.template: 65 | dest: "{{ tmsetup_codedir }}/services/tm-docker.service" 66 | group: root 67 | mode: '0644' 68 | owner: root 69 | src: services/tm-docker.service.j2 70 | notify: Restart tm-docker service 71 | 72 | - name: TMSetup - Docker Setup - Link tm-docker.service into systemd 73 | become: true 74 | ansible.builtin.file: 75 | path: /usr/lib/systemd/system/tm-docker.service 76 | src: "{{ tmsetup_codedir }}/services/tm-docker.service" 77 | state: link 78 | notify: Restart tm-docker service 79 | 80 | ... 81 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/frigate_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup - Frigate Setup tasks 3 | 4 | - name: TMSetup - Frigate Setup - Check for PCIE Coral TPUs 5 | loop: "{{ tmsetup_pcie_tpu_paths }}" 6 | ansible.builtin.stat: 7 | path: "{{ item }}" 8 | register: tmsetup_pcietpupath_stat_register 9 | 10 | - name: TMSetup Frigate Setup - Add existing PCIE Coral TPUs to list 11 | loop: "{{ tmsetup_pcietpupath_stat_register.results }}" 12 | when: item['stat']['exists'] | bool 13 | ansible.builtin.set_fact: 14 | tmsetup_pcietpus_confirmed: "{{ tmsetup_pcietpus_confirmed | default([]) + [item['stat']['path']] }}" 15 | 16 | - name: TMSetup - Frigate Setup - Create Frigate Directories 17 | become: true 18 | loop: 19 | - '{{ tmsetup_codedir }}' 20 | - '{{ tmsetup_codedir }}/frigate' 21 | - '{{ tmsetup_codedir }}/frigate/config' 22 | - '{{ tmsetup_codedir }}/frigate/storage' 23 | ansible.builtin.file: 24 | group: '{{ tmsetup_codegroup }}' 25 | mode: '0750' 26 | owner: '{{ tmsetup_codeowner }}' 27 | path: '{{ item }}' 28 | state: directory 29 | notify: Restart tm-docker service 30 | 31 | - name: TMSetup - Frigate Setup - Create Frigate docker files 32 | become: true 33 | loop: 34 | - frigate/config/config.yml 35 | - frigate/docker-compose-frigate.yaml 36 | - frigate/frigate.env 37 | ansible.builtin.template: 38 | backup: true 39 | dest: '{{ tmsetup_codedir }}/{{ item }}' 40 | force: '{{ tmsetup_force_configs }}' 41 | group: '{{ tmsetup_codegroup }}' 42 | mode: '0640' 43 | owner: '{{ tmsetup_codeowner }}' 44 | src: '{{ item }}.j2' 45 | notify: 46 | - Build container images 47 | - Restart tm-docker service 48 | ... 49 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/go2rtc_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tmsetup go2rtc setup tasks 3 | - name: TMSetup - Go2rtc Setup - Create go2rtc directory 4 | become: true 5 | ansible.builtin.file: 6 | group: root 7 | mode: '0755' 8 | owner: root 9 | path: /var/lib/go2rtc 10 | state: directory 11 | 12 | - name: TMSetup - Go2rtc Setup - Install go2rtc driver 13 | become: true 14 | ansible.builtin.get_url: 15 | dest: '/var/lib/go2rtc/go2rtc_linux_{{ tmsetup_dpkg_architecture }}' 16 | group: root 17 | mode: '0755' 18 | owner: root 19 | url: 'https://github.com/AlexxIT/go2rtc/releases/download/{{ tmsetup_go2rtc_version }}/go2rtc_linux_{{ tmsetup_dpkg_architecture }}' 20 | notify: Restart go2rtc_server service 21 | 22 | - name: TMSetup - Go2rtc Setup - Setup go2rtc rpicam settings 23 | become: true 24 | ansible.builtin.copy: 25 | dest: /var/lib/go2rtc/go2rtc.yaml 26 | group: root 27 | mode: '644' 28 | owner: root 29 | src: go2rtc.yaml 30 | notify: Restart go2rtc_server service 31 | 32 | - name: TMSetup - Go2rtc Setup - Create go2rtc_server systemd unit file 33 | become: true 34 | ansible.builtin.template: 35 | dest: "{{ tmsetup_codedir }}/services/go2rtc_server.service" 36 | group: root 37 | mode: '0644' 38 | owner: root 39 | src: services/go2rtc_server.service.j2 40 | notify: Restart go2rtc_server service 41 | 42 | - name: TMSetup - Go2rtc Setup - Link go2rtc_server.service into systemd 43 | become: true 44 | ansible.builtin.file: 45 | path: /usr/lib/systemd/system/go2rtc_server.service 46 | src: "{{ tmsetup_codedir }}/services/go2rtc_server.service" 47 | state: link 48 | notify: Restart go2rtc_server service 49 | 50 | ... 51 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup - Main tasks 3 | - name: TMSetup - Main - Set variables 4 | tags: 5 | - always 6 | ansible.builtin.import_tasks: 7 | file: 'set_vars.yml' 8 | 9 | - name: TMSetup - Main - Setup base 10 | tags: 11 | - base 12 | - always 13 | ansible.builtin.import_tasks: 14 | file: base_setup.yml 15 | 16 | - name: TMSetup - Main - Setup go2rtc driver 17 | tags: 18 | - go2rtc 19 | ansible.builtin.import_tasks: 20 | file: go2rtc_setup.yml 21 | 22 | - name: TMSetup - Main - Setup Docker 23 | tags: 24 | - docker 25 | - frigate 26 | - node-red-tm 27 | - coraltpu 28 | ansible.builtin.import_tasks: 29 | file: docker_setup.yml 30 | 31 | - name: TMSetup - Main - Setup Coral TPU Runtime Library 32 | tags: 33 | - coraltpu 34 | ansible.builtin.import_tasks: 35 | file: coraltpu_setup.yml 36 | 37 | - name: TMSetup - Main - Setup PCI Coral TPU Drivers 38 | tags: 39 | - coraltpu 40 | ansible.builtin.import_tasks: 41 | file: coraltpu_pci_setup.yml 42 | 43 | - name: TMSetup - Main - Setup Frigate 44 | tags: 45 | - frigate 46 | ansible.builtin.import_tasks: 47 | file: frigate_setup.yml 48 | 49 | - name: TMSetup - Main - Setup Node-Red-TM 50 | tags: 51 | - node-red-tm 52 | ansible.builtin.import_tasks: 53 | file: node-red-tm_docker_setup.yml 54 | 55 | - name: TMSetup - Main - Setup WiFI 56 | tags: 57 | - wifi 58 | ansible.builtin.import_tasks: 59 | file: wifi_setup.yml 60 | 61 | - name: TMSetup - Main - Start Services 62 | tags: 63 | - wifi 64 | - docker 65 | - frigate 66 | - node-red-tm 67 | - nodered 68 | - go2rtc 69 | ansible.builtin.import_tasks: 70 | file: start_services.yml 71 | ... 72 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/node-red-tm_docker_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup - Node-RED-TM Setup tasks 3 | 4 | - name: TMSetup - Node-RED-TM Setup - Check for radars 5 | loop: "{{ tmsetup_radar_paths }}" 6 | ansible.builtin.stat: 7 | path: "{{ item }}" 8 | register: tmsetup_radarpath_stat_register 9 | 10 | - name: TMSetup Node-RED-TM Setup - Add existing radars to list 11 | loop: "{{ tmsetup_radarpath_stat_register.results }}" 12 | when: item['stat']['exists'] | bool 13 | ansible.builtin.set_fact: 14 | tmsetup_radars_confirmed: "{{ tmsetup_radars_confirmed | default([]) + [item['stat']['path']] }}" 15 | 16 | - name: TMSetup - Node-RED-TM Setup - Create Node-RED-TM Directories 17 | become: true 18 | loop: 19 | - '{{ tmsetup_codedir }}' 20 | - '{{ tmsetup_codedir }}/node-red-tm' 21 | - '{{ tmsetup_codedir }}/node-red-tm/config' 22 | - '{{ tmsetup_codedir }}/node-red-tm/data' 23 | - '{{ tmsetup_codedir }}/node-red-tm/db' 24 | ansible.builtin.file: 25 | group: '{{ tmsetup_codegroup }}' 26 | mode: '0755' 27 | owner: '{{ tmsetup_codeowner }}' 28 | path: '{{ item }}' 29 | state: directory 30 | notify: Restart tm-docker service 31 | 32 | - name: TMSetup - Node Red Setup - Copy relevant static files for node red deployment 33 | become: true 34 | loop: 35 | - node-red-tm/data/flows_cred.json 36 | - node-red-tm/data/flows.json 37 | - node-red-tm/data/README.md 38 | - node-red-tm/data/settings.js 39 | ansible.builtin.copy: 40 | dest: '{{ tmsetup_codedir }}/{{ item }}' 41 | group: '{{ tmsetup_codegroup }}' 42 | mode: '0644' 43 | owner: '{{ tmsetup_codeowner }}' 44 | src: '{{ item }}' 45 | 46 | - name: TMSetup - Node-RED-TM Setup - Create Node-RED-TM docker files 47 | become: true 48 | loop: 49 | - node-red-tm/Dockerfile 50 | - node-red-tm/docker-compose-node-red-tm.yaml 51 | - node-red-tm/data/package.json 52 | ansible.builtin.template: 53 | backup: true 54 | dest: '{{ tmsetup_codedir }}/{{ item }}' 55 | force: true 56 | group: '{{ tmsetup_codegroup }}' 57 | mode: '0644' 58 | owner: '{{ tmsetup_codeowner }}' 59 | src: '{{ item }}.j2' 60 | notify: 61 | - Build container images 62 | - Restart tm-docker service 63 | 64 | - name: TMSetup - Node-RED-TM Setup - Create Node-RED-TM Configs 65 | become: true 66 | loop: 67 | - node-red-tm/config/config.yml 68 | - node-red-tm/node-red-tm.env 69 | ansible.builtin.template: 70 | backup: true 71 | dest: '{{ tmsetup_codedir }}/{{ item }}' 72 | force: '{{ tmsetup_force_configs }}' 73 | group: '{{ tmsetup_codegroup }}' 74 | mode: '0640' 75 | owner: '{{ tmsetup_codeowner }}' 76 | src: '{{ item }}.j2' 77 | notify: Restart tm-docker service 78 | ... 79 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/set_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: TMSetup - Main - Perform minimum ansible fact gathering 3 | tags: 4 | - always 5 | ansible.builtin.setup: 6 | gather_subset: min 7 | when: not ansible_facts.keys() | list | 8 | intersect(tmsetup_base_required_facts) == tmsetup_base_required_facts 9 | 10 | - name: TMSetup - Main - Set Architecture specific variables 11 | tags: 12 | - always 13 | ansible.builtin.include_vars: 14 | file: '{{ ansible_architecture }}_vars.yml' 15 | 16 | - name: TMSetup - Main - Set var for Raspberry Pi serial number 17 | ansible.builtin.slurp: 18 | src: /sys/firmware/devicetree/base/serial-number 19 | register: __tmsetup_slurp_devtree_register 20 | 21 | - name: TMSetup - Main - Set RPi serial number fact 22 | ansible.builtin.set_fact: 23 | tmsetup_raspberry_pi_serial: "{{ (__tmsetup_slurp_devtree_register['content'] | b64decode)[:-1] }}" 24 | 25 | ... 26 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/start_services.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # task file to start all services 3 | 4 | - name: TMSetup - Start Services - Start and enable all services 5 | become: true 6 | loop: 7 | - NetworkManager.service 8 | - go2rtc_server.service 9 | - tm-docker.service 10 | ansible.builtin.systemd_service: 11 | daemon_reload: true 12 | enabled: true 13 | name: "{{ item }}" 14 | state: started 15 | 16 | ... 17 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/tasks/wifi_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tmsetup wifi setup tasks 3 | - name: TMSetup - WiFi Setup - Copy wifi powersave config 4 | become: true 5 | ansible.builtin.copy: 6 | src: z-wifi-powersave-off.conf 7 | dest: /etc/NetworkManager/conf.d/z-wifi-powersave-off.conf 8 | owner: root 9 | group: root 10 | mode: '0644' 11 | notify: Restart NetworkManager service 12 | 13 | - name: TMSetup - WiFi Setup - Setup crontab 14 | become: true 15 | ansible.builtin.cron: 16 | name: Traffic Monitor Wifi Check 17 | user: '{{ tmsetup_codeowner }}' 18 | minute: '*/5' 19 | job: '/usr/bin/sudo /bin/bash {{ tmsetup_codedir }}/utils/wlan_check.sh -q' 20 | state: present 21 | ... 22 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/templates/compose.yaml.j2: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # common-services configuration file 3 | # in order: frigate, node-red 4 | # this should be located in the ~/code folder, with each container's directory 5 | ################################################################################ 6 | 7 | include: 8 | - {{ tmsetup_codedir }}/frigate/docker-compose-frigate.yaml 9 | - {{ tmsetup_codedir }}/node-red-tm/docker-compose-node-red-tm.yaml -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/templates/frigate: -------------------------------------------------------------------------------- 1 | ../../../../../docker-frigate -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/templates/node-red-tm: -------------------------------------------------------------------------------- 1 | ../../../../../node-red-tm/ -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/templates/services/go2rtc_server.service.j2: -------------------------------------------------------------------------------- 1 | # /etc/systemd/system/go2rtc_server.service 2 | # to run: sudo systemctl stop|start|restart|enable|disable go2rtc_server 3 | 4 | [Unit] 5 | Description=go2rtc 6 | After=network.target rc-local.service 7 | 8 | [Service] 9 | Restart=always 10 | WorkingDirectory=/var/lib/go2rtc/ 11 | ExecStart=/var/lib/go2rtc/go2rtc_linux_{{ tmsetup_dpkg_architecture }} 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/templates/services/tm-docker.service.j2: -------------------------------------------------------------------------------- 1 | # /etc/systemd/system/tm-docker.service 2 | # to run: sudo systemctl stop|start|restart|enable|disable tm-dockedr 3 | 4 | [Unit] 5 | Description=tm-docker 6 | Requires=docker.service 7 | After=docker.service go2rtc_server.service 8 | 9 | [Service] 10 | Restart=always 11 | User=root 12 | Group={{ tmsetup_codegroup }} 13 | TimeoutStopSec=15 14 | WorkingDirectory={{ tmsetup_codedir }} 15 | ExecStart=/usr/bin/docker compose -f compose.yaml up 16 | ExecStop=/usr/bin/docker compose -f compose.yaml down 17 | 18 | [Install] 19 | WantedBy=multi-user.target 20 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/vars/aarch64_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tmsetup specific vars for aarch64 architecture 3 | tmsetup_dpkg_architecture: arm64 4 | ... 5 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # TMSetup Variables (non-configurable) 3 | tmsetup_tm_version: '0.4.0-pre' 4 | tmsetup_go2rtc_version: 'v1.8.5' 5 | tmsetup_frigate_version: '0.15.1' 6 | tmsetup_node_red_tm_version: '4.0.9' 7 | 8 | tmsetup_remove_packages: 9 | - rpicam-apps-lite 10 | 11 | tmsetup_packages: 12 | - ca-certificates 13 | - host 14 | - screen 15 | - build-essential 16 | - cron 17 | - rpicam-apps 18 | 19 | tmsetup_base_required_facts: 20 | - 'ansible_os_family' 21 | - 'ansible_distribution' 22 | - 'ansible_distribution_major_version' 23 | - 'ansible_distribution_version' 24 | - 'ansible_architecture' 25 | - 'ansible_user_id' 26 | - 'ansible_user_dir' 27 | - 'ansible_hostname' 28 | 29 | ... 30 | -------------------------------------------------------------------------------- /script/ansible/roles/tmsetup/vars/x86_64_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tmsetup specific vars for aarch64 architecture 3 | tmsetup_dpkg_architecture: amd64 4 | ... 5 | -------------------------------------------------------------------------------- /script/ansible/setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Traffic Monitor Setup 3 | connection: local 4 | hosts: localhost 5 | ## User configurable variables. Defaults are defined here. 6 | # vars: 7 | # tmsetup_codeowner: 'tmadmin' 8 | # tmsetup_codedir: '/opt/traffic-monitor' 9 | # tmsetup_force_configs: false 10 | tasks: 11 | - name: Run tmsetup role 12 | ansible.builtin.import_role: 13 | name: tmsetup 14 | ... 15 | -------------------------------------------------------------------------------- /script/requirements: -------------------------------------------------------------------------------- 1 | ansible>=10.7.0 2 | -------------------------------------------------------------------------------- /script/tmsetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Setup ansible for install 3 | 4 | # User defined variables: 5 | VENV_DIR=~/tm_venv 6 | REBOOT_TOUCH_FILE=~/tm-reboot-required 7 | 8 | 9 | # Set Internal Variables 10 | _THIS_SCRIPT=$0 11 | _SCRIPT_DIR=$(dirname "$_THIS_SCRIPT") 12 | _START_DIR=$(pwd) 13 | _APT_UPGRADE=false 14 | _BIN_PATH=${VENV_DIR}/bin 15 | _EXIT_STATUS=0 16 | _EXTRA_ARGS="" 17 | _FORCE=false 18 | _CONFIRM=false 19 | _LOGFILE=~/tmsetup-$(date +%Y%m%d-%H%M).log 20 | _MIN_ANSIBLE_VERSION=10.7.0 21 | declare _VALID_TAGS=("base" "wifi" "docker" "frigate" "node-red-tm" "go2rtc") 22 | 23 | 24 | _pline() { # Print line function 25 | printf '============================================================\n' 26 | } 27 | _usage() { # Print usage function 28 | printf "%s [-d PATH][-o USER][-z TIMEZONE][-f|h|y] \n" "${_THIS_SCRIPT}" 29 | printf "Install Traffic Monitor software for Raspberry Pi\n\n" 30 | printf " %-20s%s\n" \ 31 | "-d PATH" "Set PATH for traffic-monitor installation (default /opt/traffic-monitor)" \ 32 | "-f" "FORCE to overwrite any existing configs with fresh copies from template" \ 33 | "-g GROUP" "Set the GROUP owner of traffic-monitor files an processes (default tmadmin)" \ 34 | "-h" "Show this usage output" \ 35 | "-l PATH" "PATH to log file for output of installer (default ${_LOGFILE})" \ 36 | "-o USER" "Set the OWNER of traffic-monitor files and processes (default tmadmin)" \ 37 | "-t TAG" "TAG the ansible-playbook command to only run a subset of plays" \ 38 | "-T" "Get a list of available tags and exit" \ 39 | "-u" "Perform system UPGRADE (apt full-upgrad) as part of installation" \ 40 | "-v" "Verbose ansible-playbook output. Call multiple times for increased verbosity" \ 41 | "-y" "Assume YES to all prompts including reboot" \ 42 | "-z TIMEZONE" "Set TIMEZONE (default America/Los_Angeles)" 43 | # "-R" "REMOVE Traffic Monitor software" # Future addition 44 | } 45 | 46 | _add_arg(){ 47 | local arg=$1 48 | _EXTRA_ARGS="${_EXTRA_ARGS} $arg" 49 | return 0 50 | } 51 | 52 | _add_var(){ 53 | local key=$1 54 | local val=$2 55 | _add_arg "--extra-vars $key=$val" 56 | return 0 57 | } 58 | 59 | _apt_upgrade(){ 60 | sudo apt update || return 1 61 | sudo apt upgrade || return 1 62 | return 0 63 | } 64 | 65 | _install_ansible() { # Check if python installed or install 66 | local venv_dir=$1 67 | dpkg -S python3-venv || ( sudo apt update && sudo apt install python3-venv ) 68 | while ! . "${venv_dir}/bin/activate" ;do 69 | python3 -m venv "${venv_dir}" || return 1 70 | done 71 | pip3 install -r "${_SCRIPT_DIR}/requirements" || return 1 72 | return 0 73 | } 74 | 75 | _log_check() { # Check if Log Path exists and create if not or exit 76 | local logfile=$1 77 | [[ -d "$(dirname "${logfile}")" ]] || mkdir -p "$(dirname "${logfile}")" || return 1 78 | 79 | # Create log file or exit 80 | touch "${logfile}" || ( printf "Unable to write to log file: %s\n" "${logfile}" && return 1 ) 81 | } 82 | 83 | _confirm_cont() { # Request to confirm continuation 84 | local conf_text=$1 85 | local cont=n 86 | if [[ "${_CONFIRM}" == false ]] 87 | then 88 | read -p "${conf_text}" -n 1 -r cont 89 | printf '\n' 90 | [[ "${cont}" =~ ^[Yy]$ ]] || return 1 91 | fi 92 | return 0 93 | } 94 | 95 | if [[ -n ${REBOOT_TOUCH_FILE} ]] ;then 96 | _add_var tmsetup_reboot_touch_file "${REBOOT_TOUCH_FILE}" 97 | touch ${REBOOT_TOUCH_FILE} || exit 1 98 | printf '0' > ${REBOOT_TOUCH_FILE} || exit 1 99 | fi 100 | 101 | # Collect command-line options 102 | while getopts ":fhTuvyd:g:l:o:t:z:" opt 103 | do 104 | case ${opt} in 105 | d) # Set PATH for install 106 | _add_var tmsetup_codedir "${OPTARG}" 107 | ;; 108 | f) # Force config overwrite 109 | _FORCE=true 110 | ;; 111 | g) # Set code GROUP 112 | _add_var tmsetup_codegroup "${OPTARG}" 113 | ;; 114 | l) # Log output to file 115 | _LOGFILE="${OPTARG}" 116 | ;; 117 | o) # Set Code OWNER 118 | _EXTRA_ARGS="${_EXTRA_ARGS} -e tmsetup_codeowner=${OPTARG}" 119 | _add_var tmsetup_codeowner "${OPTARG}" 120 | ;; 121 | t) # Set playbook TAG 122 | if [[ " ${_VALID_TAGS[*]} " =~ [[:space:]]${OPTARG}[[:space:]] ]] ;then 123 | _add_arg "--tags ${OPTARG}" 124 | else 125 | printf "Invalid Tag: %s\nExitting.\n\n" "${OPTARG}" 126 | exit 1 127 | fi 128 | ;; 129 | T) # Get TAG list 130 | printf "Valid Tags:\n" 131 | printf " %s\n" ${_VALID_TAGS[@]} 132 | exit 2 133 | ;; 134 | u) # Perform system upgrade as part of install 135 | _APT_UPGRADE=true 136 | ;; 137 | y) # Ignore confirmation requests 138 | _CONFIRM=true 139 | ;; 140 | v) # Verbose output from ansible 141 | _add_arg "-v" 142 | ;; 143 | z) # Set Time Zone 144 | _add_var tmsetup_timezone "${OPTARG}" 145 | ;; 146 | h) # Show Usage 147 | _usage 148 | exit 2 149 | ;; 150 | :) #Catch Missing arguments 151 | printf "Error: Option -%s requires a PATH argument. \n" "${OPTARG}" 152 | _usage 153 | exit 1 154 | ;; 155 | ?) #Invalid option 156 | printf "Error: Invalid Option: -%s\n\n" "${OPTARG}" 157 | _usage 158 | exit 1 159 | ;; 160 | esac 161 | done 162 | 163 | _log_check "${_LOGFILE}" || exit 1 164 | 165 | # Log all further output to logfile 166 | exec > >(tee -i "${_LOGFILE}") 167 | exec 2>&1 168 | 169 | [[ "${_APT_UPGRADE}" == "true" ]] && _apt_upgrade 170 | 171 | _install_ansible "${VENV_DIR}" 172 | if [ "${_FORCE}" = true ] 173 | then 174 | printf "Force argument passed. Existing configurations will be overwritten with defaults.\n" 175 | if ! _confirm_cont "Are you sure you wish to continue? [yN] " ;then 176 | exit 2 177 | fi 178 | _add_var tmsetup_force_configs "true" 179 | fi 180 | 181 | # Initiate ansible playbook 182 | _pline 183 | printf "Running Ansible playbook to setup Traffic Monitor\n" 184 | _pline 185 | cd "${_SCRIPT_DIR}/ansible" || exit 1 186 | . "${VENV_DIR}/bin/activate" || exit 1 187 | 188 | ANSIBLE_CMD="ansible-playbook -i localhost setup.yml ${_EXTRA_ARGS}" 189 | printf "%s\n" "${ANSIBLE_CMD}" 190 | ${ANSIBLE_CMD} 191 | 192 | # Notify and exit on error 193 | [[ "${PIPESTATUS[0]}" -eq 0 ]] || _EXIT_STATUS=1 194 | 195 | cd "${_START_DIR}" || exit "${_EXIT_STATUS}" 196 | 197 | printf "\n\n\n" 198 | _pline 199 | if [[ "${_EXIT_STATUS}" -eq 0 ]] 200 | then 201 | printf "Setup completed succesfully!\n" 202 | else 203 | printf "Setup completed with errors. Review logs at: %s\n" "${_LOGFILE}" 204 | fi 205 | printf "Full output logged to: %s\n" "${_LOGFILE}" 206 | _pline 207 | 208 | 209 | # Ask to reboot if not bypassed 210 | if [[ "$(<"${REBOOT_TOUCH_FILE}")" -ne 0 ]] ;then 211 | _pline 212 | printf "Reboot is required to complete the installation.\n" 213 | if _confirm_cont "Would you like to reboot now? [yN] " 214 | then 215 | printf "Rebooting system now.\n" 216 | sudo shutdown -r +1 "System is rebooting to finalize tmsetup.sh" 217 | printf "\n" 218 | else 219 | printf "Reboot skipped. You will need to manually reboot this device to finalize tmsetup.\nExitting...\n" 220 | fi 221 | fi 222 | 223 | exit ${_EXIT_STATUS} 224 | -------------------------------------------------------------------------------- /script/update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # script/update: Update application to run for its current checkout. 4 | 5 | set -e 6 | 7 | cd "$(dirname "$0")/.." 8 | 9 | #uncomment for re-downloading any dependencies that may need to be updated 10 | #script/bootstrap 11 | 12 | echo "==> Restarting go2rtc_server scripts..." 13 | sudo systemctl stop go2rtc_server 14 | sudo systemctl start go2rtc_server 15 | sudo systemctl enable go2rtc_server 16 | 17 | echo "==> Restarting node-red scripts..." 18 | sudo systemctl stop nodered.service 19 | sudo systemctl start nodered.service 20 | sudo systemctl enable nodered.service 21 | 22 | # node-red nodes install/reinstall 23 | npm install --unsafe-perm --no-update-notifier --no-fund --only=production --prefix ~/.node-red/ 24 | 25 | echo "==> Removing existing docker containers..." 26 | docker compose -f ~/code/compose.yaml down --remove-orphans 27 | 28 | echo "==> Updating docker containers…" 29 | # rebuild docker containers 30 | docker compose -f ~/code/compose.yaml up --detach --force-recreate --build --remove-orphans 31 | 32 | -------------------------------------------------------------------------------- /static/img/aq-dash1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/aq-dash1.png -------------------------------------------------------------------------------- /static/img/dashboard-sample-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/dashboard-sample-01.png -------------------------------------------------------------------------------- /static/img/event-car-speed-stats-daily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/event-car-speed-stats-daily.png -------------------------------------------------------------------------------- /static/img/events-counts-daily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/events-counts-daily.png -------------------------------------------------------------------------------- /static/img/events-daily-10-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/events-daily-10-day.png -------------------------------------------------------------------------------- /static/img/events-hourly-24-hours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/events-hourly-24-hours.png -------------------------------------------------------------------------------- /static/img/events-zone_radar-last5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/events-zone_radar-last5.png -------------------------------------------------------------------------------- /static/img/gitflow-trans-dark.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/gitflow-trans-dark.drawio.png -------------------------------------------------------------------------------- /static/img/tm-roadway-graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glossyio/traffic-monitor/0ca689585f9986d55d53943899f1782d87577305/static/img/tm-roadway-graphic.png -------------------------------------------------------------------------------- /utils/backup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # script/backup: Backup databases and/or media. 4 | # Optionally specify which backup. 5 | # Optionally specify local, remote, or node-red locations. 6 | # Meant to be executed on host system. 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | #-- Database backup 11 | 12 | cd ~/code/backup 13 | 14 | #backup events database 15 | tar -cvpzf events_sqlite_$(date '+%Y-%m-%d_%H-%M').tar.gz ~/code/nodered/db/tmdb.sqlite 16 | 17 | # transfer databases to server 18 | # sudo rsync -azvv --progress -e ssh ~/code/backup/events_sqlite_2024-06-03_08-40.tar.gz NAME@SERVER:DB_DUMPS/ 19 | 20 | #-- Images backup -- only do this attached locally (maximize data transfer) 21 | #clips remote backup 22 | #sudo rsync -azvv --progress -e ssh frigate/storage NAME@SERVER:DB_DUMPS/media/bak-$(date '+%Y-%m-%d')/frigate/ 23 | -------------------------------------------------------------------------------- /utils/network-wlan-ap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # script/network-wlan-ap: Turn Traffic Monitor into a wireless access Point 4 | # Turns on/off wireless access point 5 | # Allows other devices in remote deployments without a network to connect 6 | # https://www.raspberrypi.com/documentation/computers/configuration.html#host-a-wireless-network-on-your-raspberry-pi 7 | # See examples: https://networkmanager.dev/docs/api/latest/nmcli-examples.html 8 | # More examples: https://www.golinuxcloud.com/nmcli-command-examples-cheatsheet-centos-rhel/ 9 | 10 | #enable hotspot 11 | # sudo nmcli device wifi hotspot ssid password 12 | 13 | #disable hotspot 14 | #sudo nmcli device disconnect wlan0 15 | 16 | #to get hotspot to run on restart... 17 | # sudo -H nano /etc/NetworkManager/system-connections/Hotspot.nmconnection 18 | # look for `autoconnect=false` and change to true 19 | 20 | #list available wifi aps 21 | #nmcli device wifi list 22 | 23 | #connect to a password-protected wifi network 24 | #nmcli device wifi connect "$SSID" password "$PASSWORD" 25 | 26 | #turn on normal network mode 27 | #sudo nmcli device up wlan0 28 | 29 | #convenient field values retrieval for scripting 30 | #nmcli -g ip4.address connection show my-con-eth0 31 | 32 | #logs for diagnosing 33 | # journalctl -u NetworkManager 34 | # dmesg | grep wlan0 35 | 36 | #link status 37 | # ip link show 38 | # nmcli device status 39 | # iwconfig wlan0 40 | 41 | #restart network 42 | # sudo systemctl restart NetworkManager 43 | # nmcli device down wlan0 44 | # nmcli device up wlan0 45 | -------------------------------------------------------------------------------- /utils/nm-connectivity-check.conf: -------------------------------------------------------------------------------- 1 | [connectivity] 2 | uri=http://connectivity-check.ubuntu.com 3 | interval=300 4 | -------------------------------------------------------------------------------- /utils/pineboard-pcie-tpu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ########### 3 | # Install the following to use the Pine Board PCIE with Coral TPU 4 | # available at https://pineboards.io 5 | # 6 | # Cannot run this script unattended since it has reboots and does not continue 7 | ########### 8 | 9 | ### 10 | # Ensure RPi firmware is up to date: 11 | # https://pineboards.io/blogs/tutorials/how-to-update-the-firmware-on-your-raspberry-pi-5 12 | # Ensure that it shows the version dated 2023-12-06 or newer 13 | sudo rpi-eeprom-update 14 | 15 | sudo raspi-config nonint do_update 16 | 17 | # if it needs updates, uncomment and run the following: 18 | 19 | #sudo apt update 20 | #sudo apt full-upgrade 21 | #sudo reboot 22 | 23 | #sudo raspi-config 24 | #sudo reboot 25 | 26 | #sudo rpi-eeprom-update 27 | 28 | #### 29 | 30 | # Configure Google Coral Edge TPU for RPi 5 31 | # https://pineboards.io/blogs/tutorials/how-to-configure-the-google-coral-edge-tpu-on-the-raspberry-pi-5 32 | # Kernel version 6.6.30 or higher is needed for the Pineboards Hat Ai overlay 33 | #### 34 | 35 | # append this to end of config.txt, after [all] 36 | sudo echo " 37 | # Enable the PCIe External connector. 38 | dtparam=pciex1 39 | kernel=kernel8.img 40 | # Enable Pineboards Hat Ai 41 | dtoverlay=pineboards-hat-ai 42 | " >> /boot/firmware/config.txt 43 | 44 | sudo reboot 45 | 46 | uname -a 47 | # Expected output: 48 | # Linux coralpi 6.6.30-v8+ #1761 SMP PREEMPT Thu May 2 16:54:52 BST 2024 aarch64 GNU/Linux 49 | 50 | # these should exist from ./script/bootstrap 51 | #sudo apt update 52 | #echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list 53 | #curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - 54 | 55 | # may not have these from bootstrap 56 | sudo apt-get update 57 | sudo apt-get -y install cmake libedgetpu1-std devscripts debhelper dkms dh-dkms 58 | 59 | # install Gasket Driver 60 | # Clone the Gasket driver repository: 61 | 62 | git clone https://github.com/google/gasket-driver.git 63 | cd gasket-driver 64 | sudo debuild -us -uc -tc -b 65 | 66 | # Go back to the parent directory and install the built package: 67 | cd .. 68 | sudo dpkg -i gasket-dkms_1.0-18_all.deb 69 | 70 | # Step 5: Set Up the udev Rule Add a udev rule to manage device permissions: 71 | sudo sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0660\", GROUP=\"apex\"' >> /etc/udev/rules.d/65-apex.rules" 72 | 73 | # Create a new group and add your user to it: 74 | sudo groupadd apex 75 | sudo adduser $USER apex 76 | 77 | sudo reboot 78 | 79 | # Verify if the driver is loaded using the following command: 80 | sudo lspci -v 81 | 82 | # Expected output: 83 | # 0000:01:00.0 System peripheral: Global Unichip Corp. Coral Edge TPU (prog-if ff) 84 | # Subsystem: Global Unichip Corp. Coral Edge TPU 85 | # Flags: bus master, fast devsel, latency 0, IRQ 39 86 | # Memory at 1800100000 (64-bit, prefetchable) [size=16K] 87 | # Memory at 1800000000 (64-bit, prefetchable) [size=1M] 88 | # Capabilities: [80] Express Endpoint, MSI 00 89 | # Capabilities: [d0] MSI-X: Enable+ Count=128 Masked- 90 | # Capabilities: [e0] MSI: Enable- Count=1/32 Maskable- 64bit+ 91 | # Capabilities: [f8] Power Management version 3 92 | # Capabilities: [100] Vendor Specific Information: ID=1556 Rev=1 Len=008 93 | # Capabilities: [108] Latency Tolerance Reporting 94 | # Capabilities: [110] L1 PM Substates 95 | # Capabilities: [200] Advanced Error Reporting 96 | # Kernel driver in use: apex 97 | # Kernel modules: apex 98 | 99 | ### 100 | # also ref: https://coral.ai/docs/m2/get-started#2a-on-linux 101 | ### 102 | 103 | # Once rebooted, verify that the accelerator module is detected: 104 | # You should see something like this: 105 | # 03:00.0 System peripheral: Device 1ac1:089a 106 | lspci -nn | grep 089a 107 | 108 | # Also verify that the PCIe driver is loaded: 109 | # You should simply see the name repeated back: 110 | # /dev/apex_0 111 | ls /dev/apex_0 112 | -------------------------------------------------------------------------------- /utils/psu-battery: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ########### 3 | # Install the following to use the Pine Board PCIE with Coral TPU 4 | # Ref: https://forums.raspberrypi.com/viewtopic.php?t=357129 5 | # 6 | # Currently this cannot be run attended, the rpi-eeprom-config needs manual edit 7 | ########### 8 | 9 | # Both of the following should appear in /boot/config.txt (/boot/firmware/config.txt) 10 | 11 | # Enable maximum current on USB (to override protection??) 12 | # To set: `sudo nano /boot/firmware/config.txt` at end, after [all] add line `usb_max_current_enable=1` 13 | # The usb_max_current_enable option prevents the limit on USB, but the Pi still assumes a 3A PSU 14 | # The downside of setting the MAX_CURRENT flag is power brownouts 15 | # `vcgencmd get_throttled` -- which will tell you if you were running low on power at any point. 16 | # check with following command 17 | vcgencmd get_config usb_max_current_enable 18 | 19 | 20 | # Tell the RPi to accept the full current (5A) 21 | # tell the Pi that your PSU is capable of 5A, so the USB current will get the full 1.6A 22 | sudo rpi-eeprom-config --edit 23 | # add line `PSU_MAX_CURRENT=5000` 24 | # reboot system to take effect 25 | 26 | # RPi5 wattage note for powered down mode: 27 | # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#decrease-raspberry-pi-5-wattage-when-turned-off 28 | 29 | -------------------------------------------------------------------------------- /utils/rpi5-nodered-telemetry.py: -------------------------------------------------------------------------------- 1 | ################## 2 | # Adapted from https://github.com/SkatterBencher/rpi5-telemetry-python, 3 | # with additional ideas from https://www.tomshardware.com/how-to/raspberry-pi-benchmark-vcgencmd 4 | # 5 | # To run `python3 utils\rpi5-nodered-telemetry.py` after cd'ing to your traffic-monitor location. 6 | # 7 | # Output is printed in JSON format, with keys named after the various vcgencmd options. 8 | # 9 | # Adapted by MHL, September 2024. 10 | ################### 11 | 12 | import csv 13 | import os 14 | import psutil 15 | import time 16 | import fcntl 17 | import struct 18 | import json 19 | import subprocess 20 | import sys 21 | 22 | class Measure: 23 | def __init__(self, command, value=""): 24 | self.command = command 25 | self.value = value 26 | 27 | class Message: 28 | __format_string = '6I1028s' 29 | def __init__(self): 30 | self.header = [0] * 6 31 | self.byte_array = bytearray(1028) 32 | 33 | def pack(self): 34 | return struct.pack(self.__format_string, *self.header, self.byte_array) 35 | 36 | def unpack(self, data): 37 | unpacked_data = struct.unpack(self.__format_string, data) 38 | self.header = unpacked_data[:6] 39 | self.byte_array = unpacked_data[6] 40 | 41 | def get_size(self): 42 | return struct.calcsize(self.__format_string) 43 | 44 | def get_vcgencmd_output(file_desc, command): 45 | GET_GENCMD_RESULT = 0x00030080 46 | MAX_STRING = 1024 47 | 48 | p = Message() 49 | len_cmd = len(command) 50 | assert len_cmd < MAX_STRING, "vcgencmd command too long" 51 | p.header[0] = p.get_size() 52 | p.header[2] = GET_GENCMD_RESULT 53 | p.header[3] = MAX_STRING 54 | p.byte_array[:len_cmd] = map(ord, command) 55 | byte_data = bytearray(p.pack()) 56 | ret_val = fcntl.ioctl(file_desc, 0xC0086400, byte_data) 57 | if ret_val < 0: 58 | print(f"ioctl_set_msg failed: {ret_val}") 59 | return -1 60 | p.unpack(byte_data) 61 | return p.byte_array.decode("utf8").rstrip('\x00') 62 | 63 | def get_cpu_usage(): 64 | # Retrieve and return the CPU usage percentage per core 65 | return psutil.cpu_percent(interval=1, percpu=True) 66 | 67 | def decode_readmr_4(value): 68 | meanings = { 69 | "000": "SDRAM low temperature operating limit exceeded", 70 | "001": "4x refresh", 71 | "010": "2x refresh", 72 | "011": "1x refresh (default)", 73 | "100": "0.5x refresh", 74 | "101": "0.25x refresh, no derating", 75 | "110": "0.25x refresh, with derating", 76 | "111": "SDRAM high temperature operating limit exceeded", 77 | } 78 | binary_string = f"{value:03b}" # convert integer in 3-bit binary string 79 | return meanings.get(binary_string, "Meaning") 80 | 81 | def decode_throttling(throttle_hex_value): 82 | 83 | error_messages = { 84 | 0: "UV", 85 | 1: "ArmFreqCap", 86 | 2: "CurThrottle", 87 | 3: "SoftTempLimit", 88 | 16: "UV_occured", 89 | 17: "ArmFreqCap_occured", 90 | 18: "Throttle_occured", 91 | 19: "SoftTempLimit_occured", 92 | } 93 | try: 94 | # Convert hex value to binary string (remove leading '0b') 95 | binary_value = bin(int(throttle_hex_value, 16))[2:] 96 | except ValueError: 97 | return [("Invalid hex value", "N/A")] 98 | 99 | # Invert binary string (active bits are 1s) 100 | binary_value = binary_value[::-1] 101 | 102 | # Pad binary string with leading zeros to ensure consistent length 103 | binary_value = binary_value.zfill(20) 104 | 105 | # Initialize empty dictionary for results 106 | results = {} 107 | for i, message in error_messages.items(): 108 | # Check if index is within binary string length (avoid out-of-range access) 109 | if i < len(binary_value): 110 | results[message] = "Yes" if binary_value[i] == "1" else "No" 111 | else: 112 | results[message] = "No (bit out of range)" # Indicate missing bit 113 | 114 | return results 115 | 116 | def pmic_read_adc(mb): 117 | # Run the command and capture the output 118 | output = get_vcgencmd_output(mb, 'pmic_read_adc') 119 | # Decode byte output to string and split by spaces 120 | parts = output.strip().split() 121 | return [(parts[i], parts[i + 1]) for i in range(0, len(parts), 2)] 122 | 123 | # Decode the results of "get_config" 124 | def decode_config(config_values): 125 | config = {} 126 | for config_value in config_values.split('\n'): 127 | # distinguish between two formats, based on whether the config_value 128 | # contains a colon 129 | if ":" in config_value: 130 | # example: hdmi_force_cec_address:0=65535 131 | # example: hdmi_force_cec_address:1=65535 132 | # these are mapped to: 133 | # hdmi_force_cec_address: {0:65535, 1: 65535} 134 | parts = config_value.split(':') 135 | label = parts[0] 136 | value_parts = parts[1].split('=') 137 | index = value_parts[0] 138 | value = int(value_parts[1]) 139 | if label in config: 140 | config[label][index] = value 141 | else: 142 | config[label] = {} 143 | config[label][index] = value 144 | else: 145 | # example: arm_64bit=1 is mapped to: arm_64bit:1 146 | # but if the string after the equal sign is not numeric 147 | # then it is kept as a string 148 | # for example, init_uart_clock=0x2dc6c00 is mapped to init_uart_clock: "0x2dc6c00" 149 | config_components = config_value.split('=') 150 | label = config_components[0] 151 | value = config_components[1] 152 | # convert numeric (including negative) string to int 153 | try: 154 | final_value = int(value) 155 | except: 156 | final_value = value 157 | config[label] = final_value 158 | return config 159 | 160 | def main(): 161 | try: 162 | mb = os.open("/dev/vcio", os.O_RDWR) 163 | except OSError as e: 164 | print(f"Can't open device file you need to run this script as root. Error: {e}") 165 | exit(-1) 166 | 167 | fieldnames = [ 168 | "timestamp", 169 | "cpu_percent", 170 | "arm_mhz", 171 | "core_mhz", 172 | "h264_mhz", 173 | "isp_mhz", 174 | "v3d_mhz", 175 | "uart_mhz", 176 | "pwm_mhz", 177 | "emmc_mhz", 178 | "pixel_mhz", 179 | "vec_mhz", 180 | "hdmi_mhz", 181 | "dpi_mhz", 182 | "arm_temp", 183 | "core_volt", 184 | "sdram_c_volt", 185 | "sdram_i_volt", 186 | "sdram_p_volt", 187 | "3V7_WL_SW_A", 188 | "3V3_SYS_A", 189 | "1V8_SYS_A", 190 | "DDR_VDD2_A", 191 | "DDR_VDDQ_A", 192 | "1V1_SYS_A", 193 | "0V8_SW_A", 194 | "VDD_CORE_A", 195 | "3V3_DAC_A", 196 | "3V3_ADC_A", 197 | "0V8_AON_A", 198 | "HDMI_A", 199 | "3V7_WL_SW_V", 200 | "3V3_SYS_V", 201 | "1V8_SYS_V", 202 | "DDR_VDD2_V", 203 | "DDR_VDDQ_V", 204 | "1V1_SYS_V", 205 | "0V8_SW_V", 206 | "VDD_CORE_V", 207 | "3V3_DAC_V", 208 | "3V3_ADC_V", 209 | "0V8_AON_V", 210 | "HDMI_V", 211 | "EXT5V_V", 212 | "BATT_V", 213 | "readmr_4", 214 | "readmr_5", 215 | "readmr_6", 216 | "readmr_8", 217 | "throttle_hex", 218 | "UV", 219 | "ArmFreqCap", 220 | "CurThrottle", 221 | "SoftTempLimit", 222 | "UV_occured", 223 | "ArmFreqCap_occured", 224 | "Throttle_occured", 225 | "SoftTempLimit_occured", 226 | ] 227 | 228 | clocks = { 229 | "arm": Measure("measure_clock arm"), 230 | "core": Measure("measure_clock core"), 231 | "h264": Measure("measure_clock h264"), 232 | "isp": Measure("measure_clock isp"), 233 | "v3d": Measure("measure_clock v3d"), 234 | "uart": Measure("measure_clock uart"), 235 | "pwm": Measure("measure_clock pwm"), 236 | "emmc": Measure("measure_clock emmc"), 237 | "pixel": Measure("measure_clock pixel"), 238 | "vec": Measure("measure_clock vec"), 239 | "hdmi": Measure("measure_clock hdmi"), 240 | "dpi": Measure("measure_clock dpi"), 241 | } 242 | 243 | volts = { 244 | "core": Measure("measure_volts core"), 245 | "sdram_c": Measure("measure_volts sdram_c"), 246 | "sdram_i": Measure("measure_volts sdram_i"), 247 | "sdram_p": Measure("measure_volts sdram_p"), 248 | } 249 | 250 | mr = { 251 | "readmr_4": Measure("readmr 4"), 252 | "readmr_5": Measure("readmr 5"), 253 | "readmr_6": Measure("readmr 6"), 254 | "readmr_8": Measure("readmr 8"), 255 | } 256 | 257 | 258 | # Get status information 259 | timestamp = time.strftime("%Y-%m-%d %H:%M:%S") 260 | fw_version = get_vcgencmd_output(mb, "version") 261 | 262 | # Get processor usage information 263 | cpu_percent = psutil.cpu_percent(interval=1) 264 | 265 | # Get Vcgencmd metrics 266 | measure_clock = {} 267 | for key, command in clocks.items(): 268 | value = get_vcgencmd_output(mb, command.command).split('=')[1] 269 | measure_clock[key] = value 270 | 271 | arm_temp = get_vcgencmd_output(mb, "measure_temp").split('=')[1] 272 | 273 | measure_volts = {} 274 | for key, command in volts.items(): 275 | value = get_vcgencmd_output(mb, command.command).split('=')[1] 276 | measure_volts[key] = value 277 | 278 | read_mr = {} 279 | for key, command in mr.items(): 280 | value = get_vcgencmd_output(mb, command.command).split(':')[5] 281 | read_mr[key] = value 282 | 283 | # Check for throttling 284 | throttle_hex_value = get_vcgencmd_output(mb, "get_throttled").split('=')[1] 285 | throttle_status = decode_throttling(throttle_hex_value) 286 | 287 | # Measure PMIC telemetry 288 | adc_values = pmic_read_adc(mb) 289 | pmic_read_values = {} 290 | for label, value in adc_values: 291 | pmic_read_values[label] = value 292 | 293 | # Get system configuration 294 | config = decode_config(get_vcgencmd_output(mb, "get_config int")) 295 | 296 | # Get memory splits 297 | memory_split_arm = get_vcgencmd_output(mb, "get_mem arm").split('=')[1] 298 | memory_split_gpu = get_vcgencmd_output(mb, "get_mem gpu").split('=')[1] 299 | 300 | # Get memory stats from /proc/meminfo 301 | meminfo_result = subprocess.run(["cat", "/proc/meminfo"], text=True, stdout=subprocess.PIPE) 302 | meminfo_values = meminfo_result.stdout.split('\n') 303 | meminfo = {} 304 | for meminfo_value in meminfo_values: 305 | meminfo_parts = meminfo_value.split(':') 306 | if len(meminfo_parts) > 1: 307 | label = meminfo_parts[0] 308 | value = meminfo_parts[1].strip() 309 | meminfo[label] = value 310 | 311 | result = { 312 | "timestamp": timestamp, 313 | "version": fw_version, 314 | "cpu_percent": cpu_percent, 315 | "measure_clock": measure_clock, 316 | "arm_temp": arm_temp, 317 | "measure_volts": measure_volts, 318 | "read_mr": read_mr, 319 | "throttle_hex": throttle_hex_value, 320 | "throttle_status": throttle_status, 321 | "pmic_read_adc": pmic_read_values, 322 | "config": config, 323 | "memory_split_arm": memory_split_arm, 324 | "memory_split_gpu": memory_split_gpu, 325 | "meminfo": meminfo 326 | } 327 | 328 | print(json.dumps(result)) 329 | 330 | 331 | 332 | if __name__ == "__main__": 333 | main() -------------------------------------------------------------------------------- /utils/wlan_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Script will check if wlan0 is operating nominally and if not, reset the device 3 | 4 | # -q option runs this script without output unless there's an error 5 | 6 | # use "journalctl -r -t wlan_check_log" to see output 7 | QUIET="no" 8 | while getopts "q" opt; do 9 | case $opt in 10 | q) 11 | QUIET="yes" 12 | ;; 13 | \?) 14 | echo "Invalid option -$OPTARG" 15 | ;; 16 | esac 17 | done 18 | 19 | ERROR=false 20 | NETRESTARTCMD="systemctl restart NetworkManager" 21 | 22 | 23 | _logallways() { 24 | logger -t wlan_check_log -- "$*" 25 | } 26 | _logoutput(){ 27 | if [ "$QUIET" != "yes" ] 28 | then 29 | _logallways "$*" 30 | fi 31 | } 32 | 33 | _logoutput "Starting wlan_check" 34 | 35 | CONN_STATUS=$(nmcli networking connectivity) 36 | 37 | if [[ $CONN_STATUS != "full" ]] 38 | then 39 | #before network reset, log link status 40 | _logallways "***** Connectivity lost, dumping link status and restarting network interfaces *****" 41 | _IP=$(hostname -I) || true 42 | if [ "$_IP" ]; then 43 | _logallways "IP addresses: $_IP" 44 | fi 45 | _logallways "$(ip link show)" 46 | _logallways "$(nmcli device status)" 47 | _logallways "$(iwconfig wlan0)" 48 | 49 | _logallways "$($NETRESTARTCMD)" 50 | exit 1 51 | else 52 | _logallways "No network connectivity issues detected" 53 | exit 0 54 | fi 55 | -------------------------------------------------------------------------------- /utils/z-wifi-powersave-off.conf: -------------------------------------------------------------------------------- 1 | # file name starting with 'z' chosen to make sure this overwrites any 2 | # distribution default configuration 3 | 4 | # Turn off wifi power management because it seems to cause failures 5 | [connection] 6 | wifi.powersave = 2 7 | --------------------------------------------------------------------------------