Skip to content

Commit

Permalink
Initial public commit of v1 ! 🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
ryansouza committed Nov 22, 2022
0 parents commit dff5089
Show file tree
Hide file tree
Showing 22 changed files with 1,648 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
on:
push:
tags:
- '*'

permissions:
contents: write

name: Release

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Install Go
uses: actions/setup-go@v3
with:
go-version: 1.18
cache: true

- name: Fetch all tags
run: git fetch --force --tags

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 changes: 12 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
on: [push, pull_request]
name: Test
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: 1.18
cache: true
- run: go test ./...
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# built executable
aranet4-exporter

# default directory for homekit db
db/

.idea/
.idea_modules/
*.iml
*.iws
out/
dist/
nocommit*
65 changes: 65 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
project_name: aranet4-exporter
## Uncomment if/when `go mod tidy` doesn't break.
#before:
# hooks:
# - go mod tidy
# - go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
- arm
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
nfpms:
- vendor: ryry
description: Aranet4 compatible Prometheus exporter and HomeKit bridge.
maintainer: ryry
formats:
- deb
contents:
- dst: ./usr/share/lintian/overrides/aranet4-exporter
src: packaging/lintian-overrides
packager: deb
file_info:
owner: root
group: root
mode: 0644
- dst: /etc/default/aranet4-exporter
src: packaging/default.aranet4-exporter
type: config
file_info:
owner: root
group: root
mode: 0644
- dst: /usr/lib/systemd/system/aranet4-exporter.service
src: packaging/aranet4-exporter.service
file_info:
owner: root
group: root
mode: 0644
scripts:
postinstall: packaging/postinst.sh
postremove: packaging/postrm.sh
preremove: packaging/prerm.sh


76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# aranet4-exporter-go

aranet4-exporter-go is a Prometheus exporter for the [Aranet4](https://aranet.com/products/aranet4/) environmental
sensing devices. Use this to add the CO2, temperature, and humidity readings from your Aranet4 devices to your
Prometheus dashboards.

## Requirements

aranet4-exporter-go should work on most modern Linux system with Bluetooth support.

It has been tested on these platforms:

* x86 Ubuntu 22.04
* Raspberry Pi / Raspbian 3b 32-bit

## Installing

Debian-based distributions can download .deb packages from
the [releases](https://github.com/ryansouza/aranet4-exporter-go/releases).

## Setup

These instructions assume you are on a system using systemd and bluetoothctl.

1. Use bluetoothctl to pair with your Aranet4 device as follows:

```shell
$ bluetoothctl
> power on
> scan on
> scan off
# find the MAC address of the Aranet4 device in the output.
> pair XX:XX:..
# ... enter passcode shown on Aranet4 ...
> trust XX:XX:..
```

2. If `bluetoothctl` is not installed, you may need to install it:

```shell
# Raspbian
$ sudo apt-get install --no-install-recommends bluetooth pi-bluetooth bluez
# Other distros
$ sudo apt-get install --no-install-recommends bluetooth bluez
```

3. Add the MAC address and optional nickname of your devices to `/etc/default/aranet4-exporter`.

4. Run `systemctl restart aranet4-exporter`.

5. Run `journalctl status aranet4-exporter` to get the port that the daemon was started on, and add the URL to your
Prometheus collector. Example: TODO(ryansouze)

## Releasing

### Building release with goreleaser

To add a new versioned release:

```shell
$ git tag -a v0.1.0 -m "v0.1.0"
$ git push origin v0.1.0
```

### Building snapshot release with goreleaser

Snapshot build:

```shell
$ podman run --rm --privileged \
-v $PWD:/go/src/github.com/user/repo \
-w /go/src/github.com/user/repo \
-e GITHUB_TOKEN \
docker.io/goreleaser/goreleaser release --snapshot --rm-dist
```

71 changes: 71 additions & 0 deletions aranet/accessory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package aranet

import (
"github.com/brutella/hap/accessory"
"github.com/brutella/hap/characteristic"
"github.com/brutella/hap/service"
"log"
"sbinet.org/x/aranet4"
)

var qualityMap = map[int]int{1: 1, 2: 3, 3: 5}

type Accessory struct {
*accessory.A
TempSensor *service.TemperatureSensor
AranetCO2Sensor *AranetCO2Sensor
HumiditySensor *service.HumiditySensor
BatteryService *service.BatteryService
}

// NewAranetAccessory returns a Thermometer which implements model.Thermometer.
func NewAranetAccessory(info accessory.Info) *Accessory {
a := Accessory{}
a.A = accessory.New(info, accessory.TypeSensor)

a.TempSensor = service.NewTemperatureSensor()
a.AddS(a.TempSensor.S)

a.AranetCO2Sensor = NewAranetCO2Sensor()
a.AddS(a.AranetCO2Sensor.S)

a.HumiditySensor = service.NewHumiditySensor()
a.AddS(a.HumiditySensor.S)

a.BatteryService = service.NewBatteryService()
a.AddS(a.BatteryService.S)

return &a
}

func (acc *Accessory) Update(data aranet4.Data) {
acc.TempSensor.CurrentTemperature.SetValue(data.T)
acc.AranetCO2Sensor.CarbonDioxideLevel.SetValue(float64(data.CO2))
if err := acc.AranetCO2Sensor.AirQuality.SetValue(qualityMap[int(data.Quality)]); err != nil {
log.Printf("Failed to accept air quality: %v", err)
}
acc.HumiditySensor.CurrentRelativeHumidity.SetValue(data.H)
if err := acc.BatteryService.BatteryLevel.SetValue(data.Battery); err != nil {
log.Printf("Failed to accept battery level: %v", err)
}
}

type AranetCO2Sensor struct {
*service.S

AirQuality *characteristic.AirQuality
CarbonDioxideLevel *characteristic.CarbonDioxideLevel
}

func NewAranetCO2Sensor() *AranetCO2Sensor {
s := AranetCO2Sensor{}
s.S = service.New(service.TypeAirQualitySensor)

s.AirQuality = characteristic.NewAirQuality()
s.AddC(s.AirQuality.C)

s.CarbonDioxideLevel = characteristic.NewCarbonDioxideLevel()
s.AddC(s.CarbonDioxideLevel.C)

return &s
}
87 changes: 87 additions & 0 deletions aranet/aranet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package aranet

import (
"context"
"log"
"time"

"github.com/brutella/hap/accessory"
"sbinet.org/x/aranet4"
)

type AranetData interface {
Read() aranet4.Data
Room() string
}

type Aranet struct {
accessory *Accessory
id string
room string
context context.Context
ticker time.Ticker
retriever Retriever
}

func New(context context.Context, id string, room string) *Aranet {
retriever := Retriever{ID: id}
acc := NewAranetAccessory(accessory.Info{Name: "Aranet4"})
return &Aranet{
accessory: acc,
context: context,
id: id,
retriever: retriever,
room: room,
}
}

func (a *Aranet) RunUpdateLoop(verbose bool) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
log.Printf("Monitoring aranet %s as room=%s", a.id, a.room)

data := a.retriever.Read()

for {
select {
case <-ticker.C:
if verbose {
log.Println("tick")
}

if time.Since(data.Time) < data.Interval {
continue
}

if verbose {
log.Println("updating")
}

if err := a.retriever.Update(); err != nil {
log.Printf("failed update (%s): %v", a.id, err)
continue
}
data = a.retriever.Read()
if verbose {
log.Printf("got: %#v\n", data)
}

a.accessory.Update(data)
case <-a.context.Done():
log.Println("Stopped updating loop")
return
}
}
}

func (a *Aranet) Read() aranet4.Data {
return a.retriever.Read()
}

func (a *Aranet) Room() string {
return a.room
}

func (a *Aranet) Accessory() *Accessory {
return a.accessory
}
Loading

0 comments on commit dff5089

Please sign in to comment.