Skip to content

alin23/lunarsensor

Repository files navigation

Lunar Sensor

Create a custom Ambient Light Sensor on any device


This is a server that implements a custom Ambient Light Sensor for adapting monitor brightness with the Lunar macOS app.

Requirements

  • Python 3.6+

Running the server

# Installs dependencies and runs the server
make

# Runs the server without installing dependencies
make run

# If IPv6 is not available use HOST
make run HOST=0.0.0.0

# Listen on another port using the PORT variable
make run PORT=8080

Implementing light sensor reading

The file lunarsensor.py contains a server that reads lux values using the read_lux() function at the bottom of the file.

Your actual sensor reading logic should be written in that function.

Testing the server

  • Check if one-shot lux reading works
❯ curl lunarsensor.local/sensor/ambient_light

{"id":"sensor-ambient_light", "state":"0 lx", "value":0.000000}
  • Check if the EventSource is sending lux values every 2 seconds
❯ curl -N lunarsensor.local/events

event: state
data: {"id": "sensor-ambient_light", "state": "400.0 lx", "value": 400.0}

event: state
data: {"id": "sensor-ambient_light", "state": "400.0 lx", "value": 400.0}

event: state
data: {"id": "sensor-ambient_light", "state": "400.0 lx", "value": 400.0}

Implementation examples

An easily installable addon that runs the server on port 8899 and polls a sensor entity for lux values.

Open your Home Assistant instance and show the Supervisor add-on store.

adding the lunarsensor repo to HomeAssistant addons

2. Reading from a BH1750 I²C sensor

pip3 install adafruit-circuitpython-bh1750
# Do the sensor reading logic below

import board
import adafruit_bh1750

i2c = board.I2C()
sensor = adafruit_bh1750.BH1750(i2c)

def dynamic_adjust_resolution(lux):
    if lux > 300:
        sensor.resolution = adafruit_bh1750.Resolution.LOW
    elif lux > 20:
        sensor.resolution = adafruit_bh1750.Resolution.MEDIUM
    else:
        sensor.resolution = adafruit_bh1750.Resolution.HIGH

async def read_lux():
    lux = sensor.lux
    dynamic_adjust_resolution(lux)

    return lux

3. Reading from a VEML7700 I²C sensor

pip3 install adafruit-circuitpython-veml7700
# Do the sensor reading logic below

import board
import adafruit_veml7700

i2c = board.I2C()
sensor = adafruit_veml7700.VEML7700(i2c)

def dynamic_adjust_resolution(lux):
    if lux > 300:
        sensor.light_integration_time = adafruit_veml7700.ALS_25MS
        sensor.light_gain = adafruit_veml7700.ALS_GAIN_1_8
    elif lux > 100:
        sensor.light_integration_time = adafruit_veml7700.ALS_50MS
        sensor.light_gain = adafruit_veml7700.ALS_GAIN_1_4
    elif lux > 20:
        sensor.light_integration_time = adafruit_veml7700.ALS_100MS
        sensor.light_gain = adafruit_veml7700.ALS_GAIN_1
    elif lux > 10:
        sensor.light_integration_time = adafruit_veml7700.ALS_200MS
        sensor.light_gain = adafruit_veml7700.ALS_GAIN_1
    else:
        sensor.light_integration_time = adafruit_veml7700.ALS_400MS
        sensor.light_gain = adafruit_veml7700.ALS_GAIN_2

async def read_lux():
    lux = sensor.lux
    dynamic_adjust_resolution(lux)

    return lux

4. Reading from a HomeAssistant lux sensor

# Do the sensor reading logic below

HOME_ASSISTANT_URL = "http://homeassistant.local:8123"  # Replace with your HomeAssistant server URL
TOKEN = "your.jwt.token"  # Replace with your long-lived HomeAssistant API token
SENSOR_ENTITY_ID = "sensor.living_room_ambient_light"  # Replace with your sensor entity id

async def read_lux():
    async with CLIENT.get(f"{HOME_ASSISTANT_URL}/api/states/{SENSOR_ENTITY_ID}", headers={"Authorization": f"Bearer {TOKEN}"}) as response:
        sensor = await response.json()
        if not json:
            return None

        return float(sensor["state"])

5. Reading from a Phillips Hue API motion sensor

import math

# Do the sensor reading logic below

HUE_API_URL = os.getenv("HUE_API_URL") # set env variable to: http://<bridge_ip_address>/api/<user_id>
HUE_SENSOR_ID = os.getenv("HUE_SENSOR_ID") # send GET request to $HUE_API_URL/sensors and search for "Hue ambient light sensor"

async def read_lux():
    async with CLIENT.get(f"{HUE_API_URL}/sensors/{HUE_SENSOR_ID}") as response:
        sensor = await response.json()
        if not json:
            return None
        
        # Converting light level measured by sensor: 10000*log10(lux)+1
        lightlevel = sensor['state']['lightlevel']
        lux = lightlevel - 1
        lux = lux / 10000
        lux = math.pow(10, lux)

        return float(lux)

Running in Docker

Example for starting on localhost:8080

docker build -t lunarsensor .
docker run -p 127.0.0.1:8080:80 -d lunarsensor 

Pointing Lunar to the sensor server

Lunar expects to find the sensor at the lunarsensor.local address by default.

This can be changed using the defaults command on the Mac where Lunar is running.

There are three settings that affect where Lunar looks for the sensor:

  • sensorHostname set by default to lunarsensor.local
  • sensorPort set by default to 80
  • sensorPathPrefix set by default to /

For example, if you would like to have Lunar listen for sensor events at homeassistant.local:8123/lunar/events you would run the following commands:

defaults write fyi.lunar.Lunar sensorHostname homeassistant.local
defaults write fyi.lunar.Lunar sensorPort 8123
defaults write fyi.lunar.Lunar sensorPathPrefix /lunar

About

Server that mimics a Lunar ambient light sensor, with support for multiple lux data sources

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published