Skip to content

petroprotsakh/go-off-lviv

Repository files navigation

⚡ off-lviv

Go application for tracking electricity outage schedules in Lviv region, Ukraine.

Tip

TL;DR: робилось чисто для інтеграції розкладу в Home Assistant для деяких корисних автоматизацій дому. У кожного обленерго свої форми поширення даних, наш постить графіки в картинках і HTML :) Потрібен був доступний структурований графік, тому от.

Крутиться це діло тут: https://off.omko.online, поки вдома не сіли батарейки — працюватиме.

Нижче про селфхост.

Quick Start

Most useful commands are also available in the Makefile.

# Clone or download the repository
cd go-off-lviv

Standalone

Prerequisites

  • Go 1.24.2 or later
go run .

Or

go build -o off-lviv
./off-lviv

The server will start on http://localhost:8080 by default. The port can be configured using the OFF_PORT environment variable:

OFF_PORT=3000 ./off-lviv

Docker

Dockerfile is included.

docker build -t off-lviv:latest .
docker run --rm -p 8080:8080 off-lviv:latest

Docker Compose

Rename docker-compose-sample.yml to docker-compose.yml and make the necessary adjustments.

docker-compose up --build -d

Usage

Web Interface

Visit http://localhost:8080 in your browser to see:

  • Current power outage schedules for selected group
  • All available power groups (1.1, 1.2, 2.1, 2.2, etc.)
  • Outage time ranges for each group
  • Is the group currently powered on?
  • Last schedule update as provided by upstream, last fetch from upstream timestamps

The page automatically refreshes every 5 minutes.

JSON API

Get Schedule Data

GET /api/schedule

Response Example:

{
  "groups": {
    "1.1": {
      "name": "1.1",
      "has_power": true,
      "next_outage_at": "2025-11-27T11:00:00+02:00",
      "days": {
        "26.11.2025": {
          "outages": [],
          "raw_message": "Група 1.1. Електроенергія є.",
          "last_updated": "2025-11-26T19:27:00+02:00"
        },
        "27.11.2025": {
          "outages": [
            {
              "from": "11:00",
              "to": "14:30"
            }
          ],
          "raw_message": "Група 1.1. Електроенергії немає з 11:00 до 14:30.",
          "last_updated": "2025-11-26T19:05:00+02:00"
        }
      }
    },
    "1.2": {
      "name": "1.2",
      "has_power": true,
      "next_outage_at": "2025-11-27T18:00:00+02:00",
      "days": {
        "26.11.2025": {
          "outages": [
            {
              "from": "14:00",
              "to": "17:30"
            }
          ],
          "raw_message": "Група 1.2. Електроенергії немає з 14:00 до 17:30.",
          "last_updated": "2025-11-26T19:27:00+02:00"
        },
        "27.11.2025": {
          "outages": [
            {
              "from": "18:00",
              "to": "20:00"
            }
          ],
          "raw_message": "Група 1.2. Електроенергії немає з 18:00 до 20:00.",
          "last_updated": "2025-11-26T19:05:00+02:00"
        }
      }
    }
  },
  "fetched_at": "2025-11-26T21:35:31.17706+02:00"
}

Health Check

GET /health

Returns server status and last update information.

Metrics

GET /metrics

Returns usage statistics including unique users, request counts, and uptime.

GET /metrics/requests

Returns grouped request logs (last 100 unique request patterns with counts, IPs, and timestamps).

Requests are grouped by: path + IP + user agent.

Persistence and Configuration

The application does not persist any data. All data is fetched from the official website every time the server is started and lives in memory. There are no configuration options except for the port number via the OFF_PORT environment variable.

Home Assistant Integration

You can integrate the power outage schedule into Home Assistant for smart home automations like pre-charging devices, scheduling backups, or sending notifications.

Configuration

Add RESTful sensor and template for your group to configuration.yaml:

sensor:
  - platform: rest
    name: "Power Outage Schedule"
    unique_id: power_outage_schedule
    resource: https://off.omko.online/api/schedule
    scan_interval: 300
    value_template: "{{ value_json.fetched_at }}"
    json_attributes:
      - groups
      - fetched_at

template:
  - sensor:
      - name: "Power Group 1.1"
        unique_id: power_group_1_1
        state: >
          {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
          {% if groups and '1.1' in groups %}
            {% if groups['1.1'].has_power %}on{% else %}off{% endif %}
          {% else %}
            unavailable
          {% endif %}
        icon: >
          {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
          {% if groups and '1.1' in groups and groups['1.1'].has_power %}
            mdi:power-plug
          {% else %}
            mdi:power-plug-off
          {% endif %}
        attributes:
          has_power: >
            {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
            {{ groups['1.1'].has_power if groups and '1.1' in groups else none }}
          next_outage_at: >
            {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
            {{ groups['1.1'].next_outage_at if groups and '1.1' in groups else none }}
          today_date: "{{ now().strftime('%d.%m.%Y') }}"
          today_outages: >
            {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
            {% set today = now().strftime('%d.%m.%Y') %}
            {% if groups and '1.1' in groups and groups['1.1'].days and today in groups['1.1'].days %}
              {{ groups['1.1'].days[today].outages }}
            {% else %}
              []
            {% endif %}
          today_message: >
            {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
            {% set today = now().strftime('%d.%m.%Y') %}
            {% if groups and '1.1' in groups and groups['1.1'].days and today in groups['1.1'].days %}
              {{ groups['1.1'].days[today].raw_message }}
            {% else %}
              unavailable
            {% endif %}
          tomorrow_date: "{{ (now() + timedelta(days=1)).strftime('%d.%m.%Y') }}"
          tomorrow_outages: >
            {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
            {% set tomorrow = (now() + timedelta(days=1)).strftime('%d.%m.%Y') %}
            {% if groups and '1.1' in groups and groups['1.1'].days and tomorrow in groups['1.1'].days %}
              {{ groups['1.1'].days[tomorrow].outages }}
            {% else %}
              []
            {% endif %}
          tomorrow_message: >
            {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
            {% set tomorrow = (now() + timedelta(days=1)).strftime('%d.%m.%Y') %}
            {% if groups and '1.1' in groups and groups['1.1'].days and tomorrow in groups['1.1'].days %}
              {{ groups['1.1'].days[tomorrow].raw_message }}
            {% else %}
              unavailable
            {% endif %}

Note

Replace 1.1 with your actual power group number (1.1, 1.2, 2.1, 2.2, 3.1, 3.2, etc.)

If you're self-hosting, replace https://off.omko.online with your local server address (e.g., http://192.168.1.xxx:8080)

Dashboard Examples

Entities Card

Simple card showing current status, next outage and original messages for today and tomorrow:

type: entities
title: "⚡ Графік відключень - Група 1.1"
entities:
  - entity: sensor.power_group_1_1
    name: "Поточний стан"
  - type: attribute
    entity: sensor.power_group_1_1
    attribute: next_outage_at
    name: "Наступне відключення"
    format: datetime
  - type: attribute
    entity: sensor.power_group_1_1
    attribute: today_message
    name: "Сьогодні"
  - type: attribute
    entity: sensor.power_group_1_1
    attribute: tomorrow_message
    name: "Завтра"

example-hass-entity-card.png

Markdown Card

Detailed card with outage times:

type: markdown
title: "⚡ Графік відключень - Група 1.1"
content: |
  ## Поточний стан
  {% if is_state('sensor.power_group_1_1', 'on') %}
  ✅ **Світло Є**
  {% else %}
  ⭕ **Світла НЕМАЄ**
  {% endif %}
  
  ---
  
  ## Наступне відключення
  {% set next_outage = state_attr('sensor.power_group_1_1', 'next_outage_at') %}
  {% if next_outage %}
  ⏰ **{{ as_timestamp(next_outage) | timestamp_custom('%d.%m.%Y о %H:%M') }}**
  {% else %}
  Немає запланованих відключень
  {% endif %}
  
  ---
  
  ## Сьогодні ({{ state_attr('sensor.power_group_1_1', 'today_date') }})
  {% set outages = state_attr('sensor.power_group_1_1', 'today_outages') %}
  {% if outages and outages|length > 0 %}
  {% for outage in outages %}
  ⚠️ **{{ outage.from }} → {{ outage.to }}**
  {% endfor %}
  {% else %}
  ✅ Відключень немає
  {% endif %}
  
  ---
  
  ## Завтра ({{ state_attr('sensor.power_group_1_1', 'tomorrow_date') }})
  {% set outages = state_attr('sensor.power_group_1_1', 'tomorrow_outages') %}
  {% if outages and outages|length > 0 %}
  {% for outage in outages %}
  ⚠️ **{{ outage.from }} → {{ outage.to }}**
  {% endfor %}
  {% else %}
  ✅ Відключень немає
  {% endif %}

example-hass-markdown-card.png

Automation Examples

Notify when power goes out

automation:
  - alias: "⚡ Сповіщення: Світло вимкнули"
    trigger:
      - platform: state
        entity_id: sensor.power_group_1_1
        from: "on"
        to: "off"
    action:
      - service: notify.mobile_app_your_phone
        data:
          title: "⚡ Відключення світла"
          message: >
            Світло вимкнуто. {{ state_attr('sensor.power_group_1_1', 'today_message') }}

  - alias: "⚡ Сповіщення: Світло увімкнули"
    trigger:
      - platform: state
        entity_id: sensor.power_group_1_1
        from: "off"
        to: "on"
    action:
      - service: notify.mobile_app_your_phone
        data:
          title: "✅ Світло повернулось"
          message: "Електропостачання відновлено!"

Notify 30 minutes before outage

automation:
  - alias: "⚡ Попередження за 30 хвилин до відключення"
    trigger:
      - platform: time_pattern
        minutes: "/5"  # Check every 5 minutes
    condition:
      - condition: template
        value_template: >
          {% set next_outage = state_attr('sensor.power_group_1_1', 'next_outage_at') %}
          {% if next_outage %}
            {% set time_until = as_timestamp(next_outage) - now().timestamp() %}
            {{ time_until > 0 and time_until <= 1800 }}
          {% else %}
            false
          {% endif %}
    action:
      - service: notify.mobile_app_your_phone
        data:
          title: "⏰ Незабаром відключать світло"
          message: >
            {% set next_outage = state_attr('sensor.power_group_1_1', 'next_outage_at') %}
            Світло вимкнуть о {{ as_timestamp(next_outage) | timestamp_custom('%H:%M') }}.
            Зарядіть пристрої!
          data:
            priority: high
            ttl: 0

Binary Sensor Example

Create a binary sensor for simpler automations:

template:
  - binary_sensor:
      - name: "Є світло (Група 1.1)"
        unique_id: power_available_1_1
        state: >
          {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
          {{ groups['1.1'].has_power if groups and '1.1' in groups else false }}
        device_class: power
        icon: >
          {% set groups = state_attr('sensor.power_outage_schedule', 'groups') %}
          {% if groups and '1.1' in groups and groups['1.1'].has_power %}
            mdi:lightning-bolt
          {% else %}
            mdi:lightning-bolt-outline
          {% endif %}

Then use it in an automations:

automation:
  - alias: "Вимкнути обігрівачі при відключенні світла"
    trigger:
      - platform: state
        entity_id: binary_sensor.ye_svitlo_grupa_1_1
        to: "off"
    action:
      - service: climate.turn_off
        entity_id: climate.portable_heater

License

MIT License — feel free to use this for any purpose.

About

Go application for tracking electricity outage schedules

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages