Skip to main content

Solar inverter monitoring script for RS485 serial communication

Project description

Solar-RS485-Monitor

Solar inverter monitoring script for RS485/serial communication.

The collector reads inverter data, prints the parsed result as JSON, and can optionally write the result to external logging sinks.

The current parser and protocol defaults are validated for InoElectric IEPVS-3.5-G1/G2.

Optional logging sinks are implemented as separate modules under src/solar_rs485_monitor/sinks/. Telegram event notifications are implemented under src/solar_rs485_monitor/alerts/. This keeps inverter collection separate from external logging integrations such as SQLite, Google Sheets, ThingSpeak, MariaDB, and OpenSearch or Elasticsearch, while handling alert delivery separately.

Start Here

Sink Screenshots (Optional Preview)

Streamlit dashboard
Streamlit
ThingSpeak
ThingSpeak
Google Sheets
Google Sheets
OpenSearch
OpenSearch

Physical Installation Photos (Optional Preview)

Inverter unit
Inverter unit
RS485 cable path A
RS485 cable path A
RS485 cable path B
RS485 cable path B
RS485 cable terminal
RS485 cable terminal
RS485 cable routing
RS485 cable routing
FT232 RS485 to USB converter
FT232 RS485 to USB converter
RS485 cable connection
RS485 cable connection
Raspberry Pi host A
Raspberry Pi host A
Raspberry Pi host B
Raspberry Pi host B

Collected Data at a Glance

When the current parser is used with a supported InoElectric IEPVS-3.5-G1/G2 inverter, each successful read produces these core values:

Category Metric Meaning
Metadata @timestamp UTC collection time
Metadata inverter_name Configured inverter name
Metadata inverter_id Inverter ID returned by the device
DC input input_dc_voltage_v PV-side DC input voltage
DC input input_dc_current_a PV-side DC input current
DC input input_dc_power_w PV-side DC input power
AC output output_ac_voltage_v Grid-side AC output voltage
AC output output_ac_current_a Grid-side AC output current
AC output output_ac_power_w Grid-side AC output power
AC output output_ac_power_factor_pct Grid-side AC output power factor
AC output output_ac_frequency_hz Grid-side AC output frequency
Generation total_generation_kwh Total accumulated generation
Status fault_code Raw inverter fault code
Debug raw_frame_hex Raw response frame for troubleshooting

The same parsed record can be printed as JSON and optionally written to SQLite, Google Sheets, ThingSpeak, MariaDB, and OpenSearch or Elasticsearch. Telegram is used as an alert channel for fault events.

Supported Inverter Scope

The current code was written and tested for InoElectric IEPVS-3.5-G1/G2 inverters.

The request frame, response frame length, data offsets, scaling rules, CRC order, and metric interpretation are product-specific. If you use a different inverter model, check that product's specification or manual first, then update both sides of the protocol handling:

  • Request frame: set INVERTER_REQUEST_HEX to the product-specific request frame. If your environment describes this as a TCP header or protocol header, treat that product-specific header/request bytes as part of this value.
  • Response validation: set INVERTER_FRAME_LENGTH, INVERTER_DATA_LENGTH, INVERTER_CRC_ORDER, and INVERTER_ID according to the product's response format.
  • Response parsing: update parse_frame() in src/solar_rs485_monitor/collector.py if the product returns fields at different byte offsets, uses different units, or uses different scaling.

Do not assume another RS485 inverter will expose the same data layout just because the serial/TCP connection succeeds.

Current Connection Modes

The serial connection is configured with SERIAL_PORT in solar-rs485-monitor.conf.

Two modes are supported:

  1. Local USB RS485 adapter
  2. TCP access to the RS485 USB adapter attached to the remote RS485 host using socat

Internally, the code uses pyserial's serial_for_url(), so both a normal device path and a pyserial URL work with the same setting.

Quickstart With SQLite

This is the shortest path to collect data and inspect stored rows without any external logging service.

  1. Create a virtual environment:
uv venv .venv
  1. Install the package in that environment:
./.venv/bin/pip install solar-rs485-monitor
  1. Create the config file at /etc/solar-rs485-monitor.conf:
./.venv/bin/solar-rs485-monitor --print-config-template | sudo tee /etc/solar-rs485-monitor.conf >/dev/null
  1. Edit /etc/solar-rs485-monitor.conf and set at least these values:
SERIAL_PORT="/dev/ttyUSB0"
INVERTER_NAME="YOUR_INVERTER_NAME"
INVERTER_ID="1"
INVERTER_REQUEST_HEX="YOUR_INVERTER_REQUEST_HEX"
SQLITE_PATH="/tmp/solar-rs485-monitor.sqlite3"
PYTHON_VENV_PATH="/absolute/path/to/.venv"
  1. Start collection and write to SQLite:
./.venv/bin/solar-rs485-monitor --sqlite
  1. Query the latest rows:
sqlite3 -header -column /tmp/solar-rs485-monitor.sqlite3 \
"SELECT id, timestamp, input_dc_voltage_v, input_dc_power_w, output_ac_power_w, total_generation_kwh, fault_code FROM inverter_log ORDER BY id DESC LIMIT 10;"

Configuration File

Runtime configuration uses solar-rs485-monitor.conf format, parsed with python-dotenv.

Configuration lookup order:

  1. /etc/solar-rs485-monitor.conf
  2. solar-rs485-monitor.conf in the current working directory where the command is run

For a system-wide PyPI installation, create the config under /etc:

solar-rs485-monitor --print-config-template | sudo tee /etc/solar-rs485-monitor.conf >/dev/null
sudo chmod 600 /etc/solar-rs485-monitor.conf

For local development or a source checkout, keep the config in the directory where you run the command:

cp solar-rs485-monitor.conf.template solar-rs485-monitor.conf

The local solar-rs485-monitor.conf contains real credentials and must not be committed.

The config template marks measurement-only requirements with [Required]. For the minimum operation test, leave all [Sink][Optional] and [Alert][Optional] settings unused and set COLLECTOR_SINKS="" plus ALERT_CHANNELS=""; the collector will only read the inverter and print JSON. The [Sink] and [Alert] labels still identify which feature each optional setting belongs to.

General settings:

DASHBOARD_TITLE="Solar RS485 Monitor"
DASHBOARD_LANGUAGE="English"
DASHBOARD_SERVER_ADDRESS="0.0.0.0"
DASHBOARD_SERVER_PORT="8501"
DASHBOARD_SERVER_HEADLESS="true"
DASHBOARD_GATHER_USAGE_STATS="false"
DASHBOARD_RUN_ON_SAVE="false"
DASHBOARD_AUTO_REFRESH_SECONDS="60"
DASHBOARD_AUTH_ENABLED="false"
DASHBOARD_AUTH_USERS=""
DASHBOARD_AUTH_COOKIE_SECRET="CHANGE_ME_TO_A_LONG_RANDOM_SECRET"
DASHBOARD_AUTH_COOKIE_MAX_AGE_SECONDS="86400"
DASHBOARD_AUTH_COOKIE_PERSISTENT_USERS="admin"
COLLECT_INTERVAL="60"
PYTHON_VENV_PATH="/opt/myapp/.venv"
COLLECTOR_SINKS=""
ALERT_CHANNELS=""

DASHBOARD_TITLE sets the Streamlit dashboard browser title and page heading.

DASHBOARD_LANGUAGE sets the default dashboard UI language at startup. It is case-insensitive and accepts English or Korean. Users can still change language from the sidebar after loading.

The top status badge uses Bit 0 in fault_code (inverter operation flag) to determine STANDBY. Fault detection is based on Bit 1+; if any Bit 1+ is active, the badge shows FAULT. Otherwise, it shows STANDBY when Bit 0 is 1, and NORMAL when Bit 0 is 0.

DASHBOARD_AUTO_REFRESH_SECONDS sets the default auto-refresh option selected in the dashboard sidebar. Supported values are 0, 10, 30, 60, 120, 300, and 600. A value between 1 and 9 is clamped to 10 for safety.

DASHBOARD_SERVER_ADDRESS, DASHBOARD_SERVER_PORT, DASHBOARD_SERVER_HEADLESS, DASHBOARD_GATHER_USAGE_STATS, and DASHBOARD_RUN_ON_SAVE set the default Streamlit dashboard server options. Explicit command-line Streamlit options still override these values.

DASHBOARD_AUTH_ENABLED enables the built-in dashboard login. DASHBOARD_AUTH_USERS stores comma-separated username:password_hash entries generated by solar-rs485-monitor-dashboard --hash-password. DASHBOARD_AUTH_COOKIE_SECRET signs the browser login cookie, and DASHBOARD_AUTH_COOKIE_MAX_AGE_SECONDS controls how long normal logins survive refreshes and reconnects. DASHBOARD_AUTH_COOKIE_PERSISTENT_USERS defines comma-separated usernames that receive long-lived cookies. If not set, admin remains persistent for backward compatibility.

COLLECT_INTERVAL is used only when --loop is provided. A command-line --interval value implies loop mode and always overrides COLLECT_INTERVAL. Values below 10 seconds are clamped to 10 seconds to reduce accidental over-collection.

PYTHON_VENV_PATH is used by the sample systemd units to prepend ${PYTHON_VENV_PATH}/bin to PATH before launching the collector and dashboard commands.

COLLECTOR_SINKS is used only when no sink CLI flags are provided. Use all or a comma-separated list such as mariadb,thingspeak,opensearch.

ALERT_CHANNELS is used only when no alert CLI flags are provided. Use all or a comma-separated list such as telegram.

Setup

Install from PyPI after the package is published:

pip install solar-rs485-monitor

For local development with uv and the project .venv:

uv venv --python 3.10 .venv
uv pip install --python .venv/bin/python -e .

Run the installed console command:

solar-rs485-monitor

You can also run directly from a source checkout after installing dependencies:

python src/solar_rs485_monitor/collector.py

Serial Configuration

Edit solar-rs485-monitor.conf and enable exactly one SERIAL_PORT line.

For direct USB access:

SERIAL_PORT="/dev/ttyUSB0"
#SERIAL_PORT="socket://RS485_HOST_IP:9600"

For the current development setup, where the RS485 USB adapter is attached to a remote RS485 host and WSL connects to it over TCP:

#SERIAL_PORT="/dev/ttyUSB0"
SERIAL_PORT="socket://RS485_HOST_IP:9600"

Keep both lines in the file if that is convenient, but only one should be uncommented. If both are uncommented, the last parsed value can win and make the active connection unclear.

Other serial settings:

SERIAL_BAUDRATE="9600"
SERIAL_TIMEOUT="1.0"

Remote RS485 Host TCP Forwarding

In this project setup, the remote RS485 host is the device physically connected to the inverter RS485 USB converter. Because VS Code and development work may run from WSL, that host forwards /dev/ttyUSB0 over TCP with socat.

/usr/bin/socat TCP-LISTEN:9600,reuseaddr,fork FILE:/dev/ttyUSB0,raw,echo=0

An optional systemd unit sample is available at packaging/systemd/rs485-tcp-bridge.service. Install it on the remote RS485 host when you want the TCP bridge to start automatically:

cp packaging/systemd/rs485-tcp-bridge.service /etc/systemd/system/rs485-tcp-bridge.service
systemctl daemon-reload
systemctl enable --now rs485-tcp-bridge
systemctl status rs485-tcp-bridge

If many forked socat child processes remain after clients disconnect, stop the service, clear the stale processes, and consider adding -T 5 or max-children=1 to the ExecStart command for that host.

Then set solar-rs485-monitor.conf in the WSL development environment:

SERIAL_PORT="socket://RS485_HOST_IP:9600"

If the inverter does not respond over TCP, also check that the remote RS485 host serial device is using the expected speed. Depending on the adapter and OS configuration, you may need to include the baud rate in the socat file options, for example:

/usr/bin/socat TCP-LISTEN:9600,reuseaddr,fork FILE:/dev/ttyUSB0,raw,echo=0,b9600

Inverter Protocol Configuration

The inverter request and expected response format are also configured in solar-rs485-monitor.conf.

INVERTER_NAME="YOUR_INVERTER_NAME"
INVERTER_ID="1"
INVERTER_REQUEST_HEX="YOUR_INVERTER_REQUEST_HEX"
INVERTER_FRAME_LENGTH="33"
INVERTER_DATA_LENGTH="26"
INVERTER_CRC_ORDER="LH"

For the tested InoElectric IEPVS-3.5-G1/G2 setup, the request frame is:

INVERTER_REQUEST_HEX="7e0101d188"

Use a different value if your inverter manual specifies a different request frame.

INVERTER_VERIFY_CRC is optional and defaults to true.

INVERTER_VERIFY_CRC="true"

Run

Show the installed version:

solar-rs485-monitor --version

Collect once and print JSON:

solar-rs485-monitor

Override the port temporarily from the command line:

solar-rs485-monitor --port socket://RS485_HOST_IP:9600

Repeat collection using COLLECT_INTERVAL:

solar-rs485-monitor --loop

Override the repeat interval temporarily from the command line:

solar-rs485-monitor --interval 60

Write collected rows to Google Sheets:

solar-rs485-monitor --google-sheet

Write collected data to ThingSpeak:

solar-rs485-monitor --thingspeak

Send fault alert messages to Telegram:

solar-rs485-monitor --telegram

Write collected data to MariaDB:

solar-rs485-monitor --mariadb

Write collected data to SQLite:

solar-rs485-monitor --sqlite

Write collected data to OpenSearch or Elasticsearch:

solar-rs485-monitor --opensearch

Repeat collection and write to Google Sheets:

solar-rs485-monitor --interval 60 --google-sheet

Repeat collection and write to ThingSpeak:

solar-rs485-monitor --interval 60 --thingspeak

Repeat collection and send to Telegram:

solar-rs485-monitor --interval 60 --telegram

Repeat collection and write to MariaDB:

solar-rs485-monitor --interval 60 --mariadb

Repeat collection and write to SQLite:

solar-rs485-monitor --interval 60 --sqlite

Repeat collection and write to OpenSearch or Elasticsearch:

solar-rs485-monitor --interval 60 --opensearch

Multiple sinks can be enabled together:

solar-rs485-monitor --interval 60 --sqlite --google-sheet --thingspeak --mariadb --opensearch

Or enable every configured sink with one option:

solar-rs485-monitor --loop --all-sinks

With --all-sinks, SQLite, Google Sheets, ThingSpeak, and MariaDB are enabled. OpenSearch is enabled only when OPENSEARCH_URL is set. Use --opensearch explicitly if you want missing configuration to be reported as an error.

Or enable every configured alert channel with one option:

solar-rs485-monitor --loop --all-alerts

With --all-alerts, Telegram is enabled only when TELEGRAM_BOT_TOKEN and at least one target in TELEGRAM_CHAT_IDS are set. Use --telegram explicitly if you want missing configuration to be reported as an error.

External sink/alert failures are isolated. If SQLite, Google Sheets, ThingSpeak, Telegram, MariaDB, or OpenSearch fails because of a missing credential, authentication error, network error, rate limit, filesystem permission issue, or database connection issue, the collector prints an error JSON for that channel and continues the remaining work. A failed sink or alert does not stop inverter collection or block another enabled channel.

systemd Service

A sample systemd unit is available at packaging/systemd/solar-rs485-monitor.service. It reads /etc/solar-rs485-monitor.conf via EnvironmentFile and builds PATH using PYTHON_VENV_PATH:

EnvironmentFile=/etc/solar-rs485-monitor.conf
ExecStart=/usr/bin/env PATH=${PYTHON_VENV_PATH}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin solar-rs485-monitor --loop

Before installing it, set PYTHON_VENV_PATH in /etc/solar-rs485-monitor.conf to your virtualenv root, for example /opt/myapp/.venv.

The service uses the normal config lookup order. Put the daemon config at /etc/solar-rs485-monitor.conf unless you have a specific reason to keep it next to the executable. Change COLLECT_INTERVAL, COLLECTOR_SINKS, or ALERT_CHANNELS in that config file to adjust daemon behavior without editing the systemd unit.

Example install commands:

sudo cp packaging/systemd/solar-rs485-monitor.service /etc/systemd/system/
solar-rs485-monitor --print-config-template | sudo tee /etc/solar-rs485-monitor.conf >/dev/null
sudo chmod 600 /etc/solar-rs485-monitor.conf
sudo systemctl daemon-reload
sudo systemctl enable --now solar-rs485-monitor

Service control commands:

sudo systemctl status solar-rs485-monitor
sudo systemctl stop solar-rs485-monitor
sudo systemctl start solar-rs485-monitor
sudo journalctl -u solar-rs485-monitor -f

If you only want selected sinks in the service, replace --all-sinks with explicit flags such as --sqlite or --sqlite --thingspeak --mariadb --opensearch. For alerts, use --telegram or --all-alerts.

Telegram Configuration

To use Telegram alerting (--telegram or --all-alerts), configure these values in solar-rs485-monitor.conf:

TELEGRAM_BOT_TOKEN="YOUR_TELEGRAM_BOT_TOKEN"
TELEGRAM_CHAT_IDS=""
TELEGRAM_MESSAGE_THREAD_ID=""
TELEGRAM_TIMEOUT="5.0"
TELEGRAM_DISABLE_NOTIFICATION="false"
TELEGRAM_PARSE_MODE="Markdown"
TELEGRAM_SEND_SUMMARY="false"
TELEGRAM_SEND_FAULT_EVENT="true"
TELEGRAM_SEND_STANDBY_EVENT="false"

TELEGRAM_BOT_TOKEN is the bot API token from BotFather. TELEGRAM_CHAT_IDS accepts a comma-separated list of target chat/group IDs for fan-out delivery. For forum topics, set TELEGRAM_MESSAGE_THREAD_ID.

If multiple targets are configured, the alert attempts delivery to all of them. A failed target does not stop delivery to other targets.

By default, the alert channel skips normal measurements and sends messages only when a fault event is detected (excluding Bit 0, and triggered when any Bit 1+ is active). The fault event message includes key measurement values and active fault bits. Set TELEGRAM_SEND_SUMMARY="true" if you also want a summary message on each detected event.

If you want Telegram to notify inverter standby/off transition events, set TELEGRAM_SEND_STANDBY_EVENT="true". This sends a message only when fault_code Bit 0 changes from 0 to 1 (transition-based), so repeated low-power nighttime samples do not spam duplicate standby messages.

Dashboard

The Streamlit dashboard reads the same solar-rs485-monitor.conf lookup order as the collector, then queries MariaDB or SQLite and displays metric charts. MariaDB is selected by default.

Run locally:

solar-rs485-monitor-dashboard

Show the dashboard command version:

solar-rs485-monitor-dashboard --version

Open the displayed Streamlit URL in a browser. The sidebar lets you select the data source and time range up to 6 months.

The dashboard shows inverter name and ID at the top, then renders each collected metric as a separate chart. Query results are aggregated into selectable 10 second, 30 second, 1 minute, 2 minute, 5 minute, 10 minute, 15 minute, or 30 minute buckets before charting to reduce database transfer and browser rendering cost.

Dashboard server options are read from DASHBOARD_SERVER_ADDRESS, DASHBOARD_SERVER_PORT, DASHBOARD_SERVER_HEADLESS, DASHBOARD_GATHER_USAGE_STATS, and DASHBOARD_RUN_ON_SAVE in solar-rs485-monitor.conf. The default sidebar auto-refresh option can be set with DASHBOARD_AUTO_REFRESH_SECONDS, and users can still change it from the sidebar while running. The selected interval refreshes the dashboard content area without reloading the browser page. To override Streamlit server options from the command line:

solar-rs485-monitor-dashboard --server.address 0.0.0.0 --server.port 8501 --server.headless true --browser.gatherUsageStats false

Optional dashboard login uses local PBKDF2-SHA256 password hashes and does not require an external authentication service. Generate a hash, then add it to /etc/solar-rs485-monitor.conf:

solar-rs485-monitor-dashboard --hash-password
DASHBOARD_AUTH_ENABLED="true"
DASHBOARD_AUTH_USERS="admin:pbkdf2_sha256$260000$..."
DASHBOARD_AUTH_COOKIE_SECRET="replace-with-a-long-random-secret"
DASHBOARD_AUTH_COOKIE_MAX_AGE_SECONDS="86400"
DASHBOARD_AUTH_COOKIE_PERSISTENT_USERS="admin,susunwha"

For multiple users, separate entries with commas. Login state is stored in a signed browser cookie, so page refreshes do not require another login until the cookie expires. Users listed in DASHBOARD_AUTH_COOKIE_PERSISTENT_USERS are treated as persistent and remain logged in until logout unless the cookie is removed or the cookie secret changes. This is application-level login; use HTTPS, a reverse proxy, firewall rules, or a private network when exposing the dashboard outside a trusted LAN.

An optional systemd unit sample is available at packaging/systemd/solar-rs485-monitor-dashboard.service. It also uses EnvironmentFile=/etc/solar-rs485-monitor.conf and PYTHON_VENV_PATH to resolve the command from virtualenv PATH.

sudo cp packaging/systemd/solar-rs485-monitor-dashboard.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now solar-rs485-monitor-dashboard

Dashboard service control commands:

sudo systemctl status solar-rs485-monitor-dashboard
sudo journalctl -u solar-rs485-monitor-dashboard -f

Package Build

This project is structured as a PyPI package.

Build the source distribution and wheel:

uv build

The build outputs are created under dist/:

dist/solar_rs485_monitor-VERSION.tar.gz
dist/solar_rs485_monitor-VERSION-py3-none-any.whl

PyPI publishing can be handled by the GitHub Actions workflow in .github/workflows/pypi-publish.yml, or manually with uv publish after building and verifying the package.

ThingSpeak Configuration

To use --thingspeak, configure a ThingSpeak Write API Key in solar-rs485-monitor.conf.

THINGSPEAK_API_KEY="YOUR_THINGSPEAK_WRITE_API_KEY"
THINGSPEAK_TIMEOUT="5.0"

The ThingSpeak field mapping is fixed to match the configured channel:

ThingSpeak field Metric
field1 input_dc_voltage_v
field2 input_dc_current_a
field3 input_dc_power_w
field4 output_ac_voltage_v
field5 output_ac_current_a
field6 output_ac_power_w
field7 total_generation_kwh
field8 fault_code

ThingSpeak returns 0 when an update is rejected. Common causes are an invalid Write API Key or updates sent too frequently. Use an interval of at least 15 seconds for repeated updates.

MariaDB Configuration

To use --mariadb, configure these values in solar-rs485-monitor.conf:

MARIADB_HOST="YOUR_MARIADB_HOST"
MARIADB_PORT="3306"
MARIADB_USER="solar_logger"
MARIADB_PASSWORD="YOUR_MARIADB_PASSWORD"
MARIADB_DATABASE="solar_rs485_monitor"
MARIADB_TABLE="inverter_log"
MARIADB_CONNECT_TIMEOUT="5.0"

The sink expects the inverter_log table to already exist with columns matching the current collected metrics. It inserts only the parsed metric fields defined by the table schema; raw_frame_hex is printed in JSON for debugging but is not stored in MariaDB unless the table and sink are extended.

The database user only needs INSERT for normal logging. SELECT can be useful for verification and dashboards.

Example MariaDB schema and logging user:

CREATE DATABASE IF NOT EXISTS solar_rs485_monitor
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_general_ci;

USE solar_rs485_monitor;

CREATE TABLE IF NOT EXISTS inverter_log (
    id                   BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    timestamp            DATETIME(6) NOT NULL COMMENT 'UTC measurement time',
    inverter_name        VARCHAR(100) NOT NULL,
    inverter_id          TINYINT UNSIGNED NOT NULL,
    input_dc_voltage_v   SMALLINT UNSIGNED COMMENT 'DC input voltage (V)',
    input_dc_current_a   FLOAT(5,2) COMMENT 'DC input current (A)',
    input_dc_power_w     INT UNSIGNED COMMENT 'DC input power (W)',
    output_ac_voltage_v  SMALLINT UNSIGNED COMMENT 'AC output voltage (V)',
    output_ac_current_a  FLOAT(5,2) COMMENT 'AC output current (A)',
    output_ac_power_w    INT UNSIGNED COMMENT 'AC output power (W)',
    output_ac_power_factor_pct FLOAT(5,2) COMMENT 'AC output power factor (%)',
    output_ac_frequency_hz     FLOAT(5,2) COMMENT 'AC output frequency (Hz)',
    total_generation_kwh FLOAT(10,3) COMMENT 'Total generation (kWh)',
    fault_code           SMALLINT UNSIGNED DEFAULT 0 COMMENT 'Fault code',
    created_at           TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'DB insert time',
    INDEX idx_timestamp (timestamp),
    INDEX idx_inverter_id (inverter_id),
    INDEX idx_fault_code (fault_code)
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COMMENT='solar-rs485-monitor inverter log';

CREATE USER 'solar_logger'@'%' IDENTIFIED BY 'YOUR_STRONG_PASSWORD';
GRANT INSERT, SELECT ON solar_rs485_monitor.inverter_log TO 'solar_logger'@'%';

FLUSH PRIVILEGES;

The % host allows remote access from any IP. For production, restrict it to the collector host IP whenever possible.

SQLite Configuration

SQLite is the simplest local logging sink. It uses Python's standard library and does not require a database server, user account, password, or network access.

SQLITE_PATH="solar-rs485-monitor.sqlite3"
SQLITE_TABLE="inverter_log"

Run with:

solar-rs485-monitor --sqlite

The database file and table are created automatically. If SQLITE_PATH is relative, it is resolved from the current working directory where the command is run. For systemd, prefer an absolute path such as:

SQLITE_PATH="/var/lib/solar-rs485-monitor/solar-rs485-monitor.sqlite3"

The auto-created SQLite table is:

CREATE TABLE IF NOT EXISTS inverter_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp TEXT NOT NULL,
    inverter_name TEXT NOT NULL,
    inverter_id INTEGER NOT NULL,
    input_dc_voltage_v INTEGER,
    input_dc_current_a REAL,
    input_dc_power_w INTEGER,
    output_ac_voltage_v INTEGER,
    output_ac_current_a REAL,
    output_ac_power_w INTEGER,
    output_ac_power_factor_pct REAL,
    output_ac_frequency_hz REAL,
    total_generation_kwh REAL,
    fault_code INTEGER DEFAULT 0,
    raw_frame_hex TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

OpenSearch Configuration

To use --opensearch, configure these values in solar-rs485-monitor.conf:

OPENSEARCH_URL="https://YOUR_OPENSEARCH_HOST:9200"
OPENSEARCH_INDEX="solar-rs485-monitor"
OPENSEARCH_USERNAME=""
OPENSEARCH_PASSWORD=""
OPENSEARCH_TIMEOUT="5.0"
OPENSEARCH_VERIFY_TLS="true"

The sink writes each collected inverter document to:

POST /solar-rs485-monitor/_doc

Set OPENSEARCH_USERNAME and OPENSEARCH_PASSWORD together when the cluster requires basic authentication. For self-signed TLS certificates, either install the CA certificate on the host or set OPENSEARCH_VERIFY_TLS="false" for that environment.

Google Sheets Configuration

To use --google-sheet, configure these values in solar-rs485-monitor.conf:

GOOGLE_SHEET_FILE_NAME="YOUR_GOOGLE_SHEET_FILE_NAME"
GOOGLE_WORKSHEET_NAME="YOUR_GOOGLE_SHEET_NAME"

GOOGLE_WORKSHEET_NAME supports strftime patterns. For monthly tabs, set it to "%Y-%m", and writes will rotate automatically to worksheet names such as 2026-06 and 2026-07 based on collection time. If a target monthly worksheet does not exist, it is created automatically.

Also provide the Google service account fields from solar-rs485-monitor.conf.template.

The spreadsheet must be shared with the service account email:

GOOGLE_CLIENT_EMAIL="service-account@your-project-id.iam.gserviceaccount.com"

The collector creates the header row automatically if the worksheet is empty. If row 1 already exists and does not match the expected schema, the script stops with a header mismatch error.

Output

The script prints one JSON object per collection attempt.

Collected Metrics

For InoElectric IEPVS-3.5-G1/G2, the current parser interprets the response data payload as follows. Multi-byte values are decoded as big-endian unsigned integers.

Output field Data bytes Scale Unit Description
@timestamp N/A N/A UTC ISO 8601 UTC collection timestamp
inverter_name N/A N/A text Name from INVERTER_NAME
inverter_id frame byte 1 1 numeric ID Inverter ID returned by the device
input_dc_voltage_v data 0-1 1 V DC input voltage from the PV side
input_dc_current_a data 2-3 1 A DC input current from the PV side
input_dc_power_w data 4-5 1 W DC input power from the PV side
output_ac_voltage_v data 6-7 1 V Grid-side AC output voltage
output_ac_current_a data 8-9 1 A Grid-side AC output current
output_ac_power_w data 10-11 1 W Grid-side AC output power
output_ac_power_factor_pct data 12-13 0.1 % Grid-side AC output power factor
output_ac_frequency_hz data 14-15 0.1 Hz Grid-side AC output frequency
total_generation_kwh data 16-23 0.001 kWh Total accumulated generation
fault_code data 24-25 1 code Raw fault code
raw_frame_hex full frame N/A hex bytes Raw response frame for debugging

fault_code Bit Mapping

fault_code is the decimal representation of a 2-byte unsigned fault bitmask.

One response can contain more than one active fault at the same time. In other words, this is not a single enum value; it is a bitmask where multiple bits can be 1 in a single fault_code.

Simple rules:

  • If only one bit is active, fault_code equals that bit value.
  • If multiple bits are active, fault_code is the sum of active bit values.
Bit Mask (hex) Value (decimal, single-bit) Meaning (when 1)
0 0x0001 1 Inverter not operating
1 0x0002 2 PV over-voltage
2 0x0004 4 PV under-voltage
3 0x0008 8 PV over-current
4 0x0010 16 Inverter IGBT error
5 0x0020 32 Inverter over-temperature
6 0x0040 64 Grid over-voltage
7 0x0080 128 Grid under-voltage
8 0x0100 256 Grid over-current
9 0x0200 512 Grid over-frequency
10 0x0400 1024 Grid under-frequency
11 0x0800 2048 Islanding / blackout
12 0x1000 4096 Ground fault (leakage)

Simple examples:

  • Single active fault: fault_code = 2 means Bit 1 only (PV over-voltage)
  • Multiple active faults: fault_code = 72 means Bit 3 + Bit 6 (8 + 64 = PV over-current + Grid over-voltage)

Bit 0 is the operation-state bit: 1 means not operating, 0 means operating. Fault-event detection uses Bit 1+.

Successful reads include fields such as:

{
  "@timestamp": "2026-07-01T10:16:13.844550+00:00",
  "inverter_name": "Inoelectric IEPVS-3.5-G1",
  "inverter_id": 1,
  "input_dc_voltage_v": 193,
  "input_dc_current_a": 0,
  "input_dc_power_w": 54,
  "output_ac_voltage_v": 229,
  "output_ac_current_a": 0,
  "output_ac_power_w": 37,
  "output_ac_power_factor_pct": 85.0,
  "output_ac_frequency_hz": 60.0,
  "total_generation_kwh": 112.244,
  "fault_code": 0,
  "raw_frame_hex": "7e 01 02 00 1a 00 c1 00 00 00 36 00 e5 00 00 00 25 03 52 02 58 00 00 00 00 00 01 b6 74 00 00 7c 21"
}

Errors are also printed as JSON:

{
  "@timestamp": "2026-06-29T00:00:00+00:00",
  "inverter_name": "YOUR_INVERTER_NAME",
  "error": "No response from inverter"
}

Troubleshooting

  • No response from inverter: check SERIAL_PORT, remote RS485 host IP, TCP port, RS485 wiring, inverter ID, and baud rate.
  • Connection refused: socat is not running, the IP/port is wrong, or a firewall is blocking access.
  • CRC mismatch: check INVERTER_CRC_ORDER, request bytes, and whether the expected frame length matches the actual inverter response.
  • ThingSpeak update rejected: check THINGSPEAK_API_KEY and use an update interval of at least 15 seconds.
  • MARIADB_PASSWORD is not set: set the MariaDB password in solar-rs485-monitor.conf before running with --mariadb.
  • MariaDB logging failed: check MARIADB_HOST, MARIADB_PORT, firewall rules, database grants, username, password, database name, and table name.
  • SQLite unable to open database file: check SQLITE_PATH and directory write permissions.
  • OPENSEARCH_URL is not set: set the OpenSearch endpoint before running with --opensearch.
  • OpenSearch request failed: check the endpoint, index permission, username, password, TLS setting, and cluster network access.
  • Telegram request failed: check TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_IDS, bot permissions in the target group, and outbound network access to api.telegram.org.
  • Google Sheet not found or access denied: share the spreadsheet with GOOGLE_CLIENT_EMAIL.
  • Google worksheet header mismatch: check that row 1 header columns match the expected schema.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

solar_rs485_monitor-0.6.4.tar.gz (2.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

solar_rs485_monitor-0.6.4-py3-none-any.whl (81.1 kB view details)

Uploaded Python 3

File details

Details for the file solar_rs485_monitor-0.6.4.tar.gz.

File metadata

  • Download URL: solar_rs485_monitor-0.6.4.tar.gz
  • Upload date:
  • Size: 2.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for solar_rs485_monitor-0.6.4.tar.gz
Algorithm Hash digest
SHA256 483bfaf84eab751d4c0c2d1bf933089f3f1abc394a0a6c2634dadea94782ded7
MD5 7aba4be912f52c53540664aa32d41b3c
BLAKE2b-256 5b627322a9bb8aa1d38638b441311ef8716344db7b61bace67298facec97098a

See more details on using hashes here.

Provenance

The following attestation bundles were made for solar_rs485_monitor-0.6.4.tar.gz:

Publisher: pypi-publish.yml on call518/Solar-RS485-Monitor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solar_rs485_monitor-0.6.4-py3-none-any.whl.

File metadata

File hashes

Hashes for solar_rs485_monitor-0.6.4-py3-none-any.whl
Algorithm Hash digest
SHA256 a8a9e6bdb2b08fb64a822fdcd510404960f5f1e01310c2147aef631abd08151d
MD5 93b56926e2d3c3a96851fa9412e846bd
BLAKE2b-256 1cd0018b7bce568023326b7ad30e2f479322f81071cb019ff2ded4b7d77dacf4

See more details on using hashes here.

Provenance

The following attestation bundles were made for solar_rs485_monitor-0.6.4-py3-none-any.whl:

Publisher: pypi-publish.yml on call518/Solar-RS485-Monitor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page