Skip to content

Commit

Permalink
Run as a service
Browse files Browse the repository at this point in the history
we add a watchdog to restart if things break. we add a unit file.
modified readme to document this mode, as well as running with telegraf.
  • Loading branch information
igor47 committed Sep 14, 2020
1 parent 92e1b9b commit 2ab7b80
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 30 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,55 @@ $ pip install poetry

## Running

Use poetry to install dependencies:

```bash
$ poetry install
```

Invoke with poetry:

```bash
$ poetry run ./main.py
```

You can get help by passing `--help`.

### Options

By default, this program will scan a few possible `tty` ports to find a PMS7003.
You may wish to pass the location explicitly, by using `--port <port>`.

Also, by default, the program will print quality measurements to the terminal.
It will also print them, in [influxdb line protocol format](https://docs.influxdata.com/influxdb/v1.8/write_protocols/line_protocol_tutorial/), to `measurements.log`.
You may wish to disable printing to the terminal with `--log-only`, and customize the location of the log file with `--log-path`.

## Running as a `systemd` service

On a recent Linux, you can run this as a service.
Modify the included `mini-aqm.service` file, and edit the `WorkingDirectory` and `ExecStart` variables.
`WorkingDirectory` should point at the location where you have this repo checked out.
`ExecStart` (and `ExecStartPre`) should have the path to your `poetry` binary -- find it with `which poetry`.
You may also wish to customize the arguments to `main.py`, for instance to set `--log-path`.

To install the service:

```bash
cat mini-aqm.service | sudo tee /etc/systemd/system/mini-aqm.service
sudo systemctl daemon-reload
sudo systemctl start mini-aqm
```

## With `telegraf`

You might want to pull the measurements into a time series database using [`telegraf`](https://github.com/influxdata/telegraf).
You can do so by using the [`tail`](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tail) plugin.
The configuration looks like this:

```
[[inputs.tail]]
files = ["/home/igor47/repos/mini-aqm/measurements.log"]
```

Customize the path where your `measurements.log` file is found.
You may wish to pass an explicit path to `main.py` using `--log-path <path>`.
50 changes: 29 additions & 21 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from colorama import Fore, Style
import logging
import os
import time
from typing import Tuple
import systemd_watchdog
from typing import Tuple, Optional

from influxdb_logger import InfluxdbLogger
from pms7003 import PMS7003, PMSData
Expand Down Expand Up @@ -71,10 +71,9 @@ def print_pm(data: PMSData) -> None:
@click.command()
@click.option(
"--port",
default="/dev/ttyUSB0",
default=None,
help="Location of PMS7003 TTY device",
required=True,
show_default=True,
show_default="scans possible ports for devices",
)
@click.option(
"--debug/--no-debug",
Expand All @@ -94,36 +93,45 @@ def print_pm(data: PMSData) -> None:
show_default=True,
)
def main(
port: str, debug: bool, log_only: bool, log_path: str
port: Optional[str], debug: bool, log_only: bool, log_path: str
) -> None:
if not os.access(port, mode=os.R_OK, follow_symlinks=True):
devs = PMS7003.get_all(only=port)
if not devs:
click.echo(
f"{Fore.RED}cannot access {port}; check path and permissions", err=True
f"{Fore.RED}"
f"cannot find PMS7003 on any checked port; check path and permissions"
f"{Style.RESET_ALL}",
err=True
)
return

logger = InfluxdbLogger(log_path)
tags = {"type": "PMS7003", "id": port}
click.echo(
f"{Fore.BLUE}"
f"writing influxdb measurement {logger.MEASUREMENT} to {logger.path}"
f"{Style.RESET_ALL}"
)

dev = PMS7003(port)
click.echo(f"{Fore.GREEN}beginning to read data from {port}...{Style.RESET_ALL}")
for dev in devs:
click.echo(f"{Fore.GREEN}beginning to read data from {dev.id}...{Style.RESET_ALL}")

# systemd watchdog, in case this is running as a systemd service
wd = systemd_watchdog.watchdog()
wd.ready()

while True:
data = dev.read()
if debug:
print_verbose(data)
else:
logger.emit(
fields={k: v for k, v in data._asdict().items() if k.startswith("pm")},
tags=tags,
)
if not log_only:
print_pm(data)
wd.ping()
for dev in devs:
data = dev.read()
if debug:
print_verbose(data)
else:
logger.emit(
fields={k: v for k, v in data._asdict().items() if k.startswith("pm")},
tags={"type": "PMS7003", "id": dev.id},
)
if not log_only:
print_pm(data)

if __name__ == "__main__":
main()
21 changes: 21 additions & 0 deletions mini-aqm.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[Unit]
Description=Monitors and collects data from PMS7003
Documentation=https://github.com/igor47/mini-aqm https://igor.moomers.org/minimal-viable-air-quality
Requires=local-fs.target
After=local-fs.target

[Service]
WorkingDirectory=/home/igor47/repos/mini-aqm
ExecStartPre=/home/igor47/.asdf/installs/python/3.8.3/bin/poetry install
ExecStart=/home/igor47/.asdf/installs/python/3.8.3/bin/poetry run ./main.py

Type=notify
WatchdogSec=10
RestartSec=10
Restart=always

User=igor47
Group=igor47

[Install]
WantedBy=multi-user.target
38 changes: 30 additions & 8 deletions pms7003.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
http://eleparts.co.kr/data/_gextends/good-pdf/201803/good-pdf-4208690-1.pdf
"""
import logging
import os
import serial
import struct
import time
from typing import Any, Dict, NamedTuple
from typing import Any, Dict, NamedTuple, List, Optional


class PMSData(NamedTuple):
Expand Down Expand Up @@ -44,20 +45,41 @@ class PMS7003(object):
HEADER_LOW = int("0x4d", 16)

# UART / USB Serial : 'dmesg | grep ttyUSB'
USB0 = "/dev/ttyUSB0"
UART = "/dev/ttyAMA0"
S0 = "/dev/serial0"

# USE PORT
DEFAULT_PORT = S0
POSSIBLE_PORTS = {
'USB0': "/dev/ttyUSB0",
'USB1': "/dev/ttyUSB1",
'USB2': "/dev/ttyUSB2",
'UART': "/dev/ttyAMA0",
'S0': "/dev/serial0",
}

# Baud Rate
SERIAL_SPEED = 9600

# give up after trying to read for this long
READ_TIMEOUT_SEC = 2

def __init__(self, port: str = DEFAULT_PORT):
@classmethod
def get_all(cls, only: Optional[str] = None) -> List['PMS7003']:
"""checks several possible locations for PMS7003 devices
returns all valid locations
"""
devs = []

possible = [only] if only else cls.POSSIBLE_PORTS.values()
for port in possible:
if os.access(port, mode=os.R_OK, follow_symlinks=True):
try:
dev = PMS7003(port)
if dev.read():
devs.append(dev)
except:
pass

return devs

def __init__(self, port: str = POSSIBLE_PORTS["S0"]):
self.port = port
self.buffer: bytes = b""
self.log = logging.getLogger(str(self))
Expand Down
14 changes: 13 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ python = "^3.8"
pyserial = "^3.4"
click = "^7.1.2"
colorama = "^0.4.3"
systemd-watchdog = "^0.9.0"

[tool.poetry.dev-dependencies]
black = "^20.8b1"
Expand Down

0 comments on commit 2ab7b80

Please sign in to comment.