Go application for tracking electricity outage schedules in Lviv region, Ukraine.
Tip
TL;DR: робилось чисто для інтеграції розкладу в Home Assistant для деяких корисних автоматизацій дому. У кожного обленерго свої форми поширення даних, наш постить графіки в картинках і HTML :) Потрібен був доступний структурований графік, тому от.
Крутиться це діло тут: https://off.omko.online, поки вдома не сіли батарейки — працюватиме.
Нижче про селфхост.
Most useful commands are also available in the Makefile.
# Clone or download the repository
cd go-off-lviv- Go 1.24.2 or later
go run .Or
go build -o off-lviv
./off-lvivThe server will start on http://localhost:8080 by default.
The port can be configured using the OFF_PORT environment variable:
OFF_PORT=3000 ./off-lvivDockerfile is included.
docker build -t off-lviv:latest .
docker run --rm -p 8080:8080 off-lviv:latestRename docker-compose-sample.yml to docker-compose.yml and make the necessary adjustments.
docker-compose up --build -dVisit 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.
GET /api/scheduleResponse 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"
}GET /healthReturns server status and last update information.
GET /metricsReturns usage statistics including unique users, request counts, and uptime.
GET /metrics/requestsReturns grouped request logs (last 100 unique request patterns with counts, IPs, and timestamps).
Requests are grouped by: path + IP + user agent.
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.
You can integrate the power outage schedule into Home Assistant for smart home automations like pre-charging devices, scheduling backups, or sending notifications.
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)
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: "Завтра"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 %}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: "Електропостачання відновлено!"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: 0Create 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_heaterMIT License — feel free to use this for any purpose.

