{"id":27944569,"url":"https://github.com/sourcehawk/plc-exporter","last_synced_at":"2026-04-30T10:03:47.180Z","repository":{"id":291607540,"uuid":"978152945","full_name":"sourcehawk/plc-exporter","owner":"sourcehawk","description":"A prometheus exporter and dashboard provider for PLCs","archived":false,"fork":false,"pushed_at":"2025-05-05T16:24:39.000Z","size":212,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-11T15:31:33.658Z","etag":null,"topics":["grafana","grafana-dashboard","iot","monitoring","plc","prometheus-exporter","prometheus-metrics","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sourcehawk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-05T14:49:15.000Z","updated_at":"2025-05-05T16:29:56.000Z","dependencies_parsed_at":"2025-05-05T16:46:48.092Z","dependency_job_id":"04bda104-17fa-475d-86bf-41891b974f11","html_url":"https://github.com/sourcehawk/plc-exporter","commit_stats":null,"previous_names":["sourcehawk/plc-exporter"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sourcehawk/plc-exporter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcehawk%2Fplc-exporter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcehawk%2Fplc-exporter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcehawk%2Fplc-exporter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcehawk%2Fplc-exporter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sourcehawk","download_url":"https://codeload.github.com/sourcehawk/plc-exporter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcehawk%2Fplc-exporter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32460781,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"online","status_checked_at":"2026-04-30T02:00:05.929Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["grafana","grafana-dashboard","iot","monitoring","plc","prometheus-exporter","prometheus-metrics","python3"],"created_at":"2025-05-07T12:52:56.756Z","updated_at":"2026-04-30T10:03:47.153Z","avatar_url":"https://github.com/sourcehawk.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PLC exporter\n\nThis is a simple metric exporter and dashboard provider for PLCs. It uses pymodbus to read variables from the PLC given a configuration file and exposes them as Prometheus metrics.\n\n## Table of Contents\n\n- [Running](#installation-and-running)\n- [Configuration](#configuration)\n  - [Coils (Digital Output)](#coils-digital-output)\n  - [Discrete Inputs (Digital Input)](#discrete-inputs-digital-input)\n  - [Input Registers (Analog Input)](#input-registers-analog-input)\n  - [Holding Registers (Analog Output)](#holding-registers-analog-output)\n  - [Types](#types)\n- [Metrics](#metrics)\n  - [Static metric naming](#static-metric-naming)\n  - [Dynamic metric naming](#dynamic-metric-naming)\n  - [Default metric labels](#default-metric-labels)\n  - [Dynamic metric sample](#dynamic-metric-sample)\n  - [Static metric sample](#static-metric-sample)\n- [Grafana dashboard](#grafana-dashboard)\n- [Development](#development)\n  - [Project setup](#project-setup)\n  - [Grafana dashboard and alert development](#grafana-dashboard-and-alert-development)\n  - [PLC access](#plc-access)\n\n## Installation and running\n\nInstall the exporter using pip:\n\n```bash\npip install plc_exporter\n```\n\nRun the exporter using the following command:\n\n```bash\nplc_exporter --config /path/to/config.yaml\n```\n\nThe exporter is also available as a docker image:\n\n```bash\ndocker pull ghcr.io/sourcehawk/plc-exporter:latest\n```\n\nTo run the docker image, use the following command:\n\n```bash\ndocker run \\\n  --name myplc \\\n  -p 9075:9075 \\\n  -v ./examples/config.yaml:/config.yml \\\n  ghcr.io/sourcehawk/plc-exporter:latest \\\n  --config=/config.yml\n```\n\n## Configuration\n\nThe exporter is configured using a YAML file. See the [base config](./config.yml) for the structure.\n\n### Coils (Digital Output)\n\n| Parameter   | Description                                          | Allowed Values                               |\n| ----------- | ---------------------------------------------------- | -------------------------------------------- |\n| name        | Descriptive name for the coil                        | Letters, numbers and underscores             |\n| description | Explanation of what the coil does                    | Any string                                   |\n| address     | Address of the coil in hexadecimal or decimal format | hex `0x0000` to `0xFFFF` or int `0` to `255` |\n| type        | Data type of the input register value                | `bool`                                       |\n\n### Discrete Inputs (Digital Input)\n\n| Parameter   | Description                                           | Allowed Values                               |\n| ----------- | ----------------------------------------------------- | -------------------------------------------- |\n| name        | Descriptive name for the discrete input               | Letters, numbers and underscores             |\n| description | Explanation of the input's function                   | Any string                                   |\n| address     | Address of the input in hexadecimal or decimal format | hex `0x0000` to `0xFFFF` or int `0` to `255` |\n| type        | Data type of the input register value                 | `bool`                                       |\n\n### Input Registers (Analog Input)\n\n| Parameter   | Description                                              | Allowed Values                               |\n| ----------- | -------------------------------------------------------- | -------------------------------------------- |\n| name        | Descriptive name for the input register                  | Letters, numbers and underscores             |\n| description | Explanation of the register's function                   | Any string                                   |\n| address     | Address of the register in hexadecimal or decimal format | hex `0x0000` to `0xFFFF` or int `0` to `255` |\n| type        | Data type of the input register value (default `uint16`) | See types table                              |\n| size        | Number of bytes (chars) used (default 1)                 | 1 to 255                                     |\n\nWhen `type` is set to `string`, the `size` parameter is used to specify the number of characters in the string.\n\n### Holding Registers (Analog Output)\n\n| Parameter   | Description                                                | Allowed Values                               |\n| ----------- | ---------------------------------------------------------- | -------------------------------------------- |\n| name        | Descriptive name for the holding register                  | Letters, numbers and underscores             |\n| description | Explanation of the register's function                     | Any string                                   |\n| address     | Address of the register in hexadecimal or decimal format   | hex `0x0000` to `0xFFFF` or int `0` to `255` |\n| type        | Data type of the holding register value (default `uint16`) | See types table                              |\n| size        | Number of bytes (chars) used (default 2)                   | 1 to 255                                     |\n\nWhen `type` is set to `string`, the `size` parameter is used to specify the number of characters in the string.\n\n### Types\n\n| Type    | Description           | Size (bytes)   | Register count      |\n| ------- | --------------------- | -------------- | ------------------- |\n| bool    | Boolean               | 1              | 1                   |\n| uint8   | Unsigned 8-bit int    | 1              | 1                   |\n| int8    | Signed 8-bit int      | 1              | 1                   |\n| uint16  | Unsigned 16-bit int   | 2              | 1                   |\n| int16   | Signed 16-bit int     | 2              | 1                   |\n| uint32  | Unsigned 32-bit int   | 4              | 2                   |\n| int32   | Signed 32-bit int     | 4              | 2                   |\n| uint64  | Unsigned 64-bit int   | 8              | 4                   |\n| int64   | Signed 64-bit int     | 8              | 4                   |\n| float16 | IEEE 754 16-bit float | 2              | 1                   |\n| float32 | IEEE 754 32-bit float | 4              | 2                   |\n| float64 | IEEE 754 64-bit float | 8              | 4                   |\n| char    | ASCII character       | 1              | 1                   |\n| string  | ASCII string          | User specified | Math.ceil(size / 2) |\n\n## Metrics\n\nThe exporter can be configured in `static` or `dynamic` mode. In static mode, the metrics are named according to the `name` parameter in the configuration file. In dynamic mode, the metrics are named according to the register type and uniquely identified by the `name` label.\n\nWhich mode to use depends on the use case. If you are creating a dashboard that will be used for multiple devices, the dynamic mode is recommended. If you are creating a dashboard for a specific device, the static mode might be more convenient.\n\nAll metrics generated by the exporter are prefixed with the `namespace` parameter of the exporter configuration.\n\n### Static metric naming\n\nIn static mode the metrics generated for all types except strings will be `{namespace}_{name}`. I.e if the namespace is `plc` and the name is `inner_temperature`, the metric will be `plc_inner_temperature`.\n\nIf the metric is a string, there will be multiple metrics with the same name but with a unique `index` label. The `index` is the position of the character in the string that is being built. Each of the generated metrics will have the value of the ASCII code of the character it represents. For example, if the name is `material` and the string is `TEST`, the metrics will be :\n\n- `plc_material{index=\"0\", value_type=\"ascii\"}` with the value `84` (The ASCII code for `T`)\n- `plc_material{index=\"1\", value_type=\"ascii\"}` with the value `69` (The ASCII code for `E`)\n- `plc_material{index=\"2\", value_type=\"ascii\"}` with the value `83` (The ASCII code for `S`)\n- `plc_material{index=\"3\", value_type=\"ascii\"}` with the value `84` (The ASCII code for `T`)\n\n### Dynamic metric naming\n\nIn dynamic mode the metrics generated for all types will be `{namespace}_{register_type}`. I.e if the namespace is `plc` and the register type is `coils`, the metric will be `plc_coils`. If the metric was for `inner_temperature` of type `coils` and the namespace is `plc`, the metric will be `plc_coils{name=\"inner_temperature\"}`.\n\n### Default metric labels\n\nAll metrics have the following labels\n\n- `plc`: Is set to the `identifier` parameter in the configuration file.\n- `start_address`: The start address of the register in the PLC as a hexadecimal string\n- `value_type`: The type of the value (See [Types table](#types)). **Note that strings and characters will have the type set to `ascii`**.\n- `index`: The order (index) of the character in the string (0, 1, 2, ...). If not a string, this label is set to `None`.\n- `register_type` (Always present in `static` metric layout. Available in `dynamic` metric layout for `read_time_ms` + `error_count` metrics): The type of register (`coil`, `discrete_input`, `input_register`, `holding_register`)\n- `name` (In `dynamic` metric layout): The name of the metric as specified in the configuration file.\n\n### Dynamic metric sample\n\nGenerated from [examples/config.yml](./examples/config.yml) with `metric_layout` set to `dynamic`.\n\nNOTE: Removed the `read_time_ms` and `error_count` metrics for brevity.\n\n```txt\n# HELP plc_connection_up Connection status to the PLC\n# TYPE plc_connection_up gauge\nplc_connection_up{manufacturer=\"test\",model=\"test-2000\",plc=\"master\"} 1.0\n# HELP plc_coils Coils represent discrete outputs, which are binary values and are used to control physical devices like relays, motors, lights, or any output devices connected to the PLC.They can be read and written to.\n# TYPE plc_coils gauge\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"light_green\",plc=\"master\",start_address=\"0x0001\",value_type=\"bool\"} 1.0\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"light_red\",plc=\"master\",start_address=\"0x0003\",value_type=\"bool\"} 0.0\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"light_yellow\",plc=\"master\",start_address=\"0x0005\",value_type=\"bool\"} 0.0\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"chamber_light_blue\",plc=\"master\",start_address=\"0x0007\",value_type=\"bool\"} 1.0\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"chamber_light_purple\",plc=\"master\",start_address=\"0x0009\",value_type=\"bool\"} 1.0\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"crane_motor_power\",plc=\"master\",start_address=\"0x000d\",value_type=\"bool\"} 1.0\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"crane_motor_direction_left\",plc=\"master\",start_address=\"0x000e\",value_type=\"bool\"} 0.0\nplc_coils{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"crane_motor_direction_right\",plc=\"master\",start_address=\"0x000f\",value_type=\"bool\"} 1.0\n# HELP plc_discrete_inputs Discrete inputs are binary values that represent the state of physical devices like sensors, switches, or any input devices connected to the PLC. They are read-only and cannot be written to.\n# TYPE plc_discrete_inputs gauge\nplc_discrete_inputs{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"emergency_stop\",plc=\"master\",start_address=\"0x0004\",value_type=\"bool\"} 1.0\nplc_discrete_inputs{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"crane_lift_at_horizontal_max\",plc=\"master\",start_address=\"0x0005\",value_type=\"bool\"} 1.0\nplc_discrete_inputs{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"crane_lift_at_horizontal_min\",plc=\"master\",start_address=\"0x0006\",value_type=\"bool\"} 0.0\n# HELP plc_input_registers Input registers are 16-bit registers that store numeric values. They are read-only and cannot be written to.\n# TYPE plc_input_registers gauge\nplc_input_registers{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"temperature\",plc=\"master\",start_address=\"0x0001\",value_type=\"uint16\"} 254.0\nplc_input_registers{index=\"0\",manufacturer=\"test\",model=\"test-2000\",name=\"material_name\",plc=\"master\",start_address=\"0x0003\",value_type=\"ascii\"} 70.0\nplc_input_registers{index=\"1\",manufacturer=\"test\",model=\"test-2000\",name=\"material_name\",plc=\"master\",start_address=\"0x0003\",value_type=\"ascii\"} 101.0\nplc_input_registers{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"material_scalar\",plc=\"master\",start_address=\"0x0005\",value_type=\"float32\"} 3.14\n# HELP plc_holding_registers Holding registers are 16-bit registers that store numeric values. They can be read and written to.\n# TYPE plc_holding_registers gauge\nplc_holding_registers{index=\"None\",manufacturer=\"test\",model=\"test-2000\",name=\"target_temperature\",plc=\"master\",start_address=\"0x0001\",value_type=\"uint16\"} 870.0\nplc_holding_registers{index=\"0\",manufacturer=\"test\",model=\"test-2000\",name=\"test_string\",plc=\"master\",start_address=\"0x0003\",value_type=\"ascii\"} 84.0\nplc_holding_registers{index=\"1\",manufacturer=\"test\",model=\"test-2000\",name=\"test_string\",plc=\"master\",start_address=\"0x0003\",value_type=\"ascii\"} 69.0\nplc_holding_registers{index=\"2\",manufacturer=\"test\",model=\"test-2000\",name=\"test_string\",plc=\"master\",start_address=\"0x0004\",value_type=\"ascii\"} 83.0\nplc_holding_registers{index=\"3\",manufacturer=\"test\",model=\"test-2000\",name=\"test_string\",plc=\"master\",start_address=\"0x0004\",value_type=\"ascii\"} 84.0\n```\n\n### Static metric sample\n\nGenerated from [examples/config.yml](./examples/config.yml) with `metric_layout` set to `static`.\n\nNOTE: Removed the `read_time_ms` and `error_count` metrics for brevity.\n\n```txt\n# HELP plc_connection_up Connection status to the PLC\n# TYPE plc_connection_up gauge\nplc_connection_up{manufacturer=\"test\",model=\"test-2000\",plc=\"master\"} 1.0\n# HELP plc_light_green Turns on the green light when enabled (1)\n# TYPE plc_light_green gauge\nplc_light_green{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x0001\",value_type=\"bool\"} 1.0\n# HELP plc_light_red Turns on the red light when enabled (1)\n# TYPE plc_light_red gauge\nplc_light_red{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x0003\",value_type=\"bool\"} 0.0\n# HELP plc_light_yellow Turns on the yellow light when enabled (1)\n# TYPE plc_light_yellow gauge\nplc_light_yellow{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x0005\",value_type=\"bool\"} 0.0\n# HELP plc_chamber_light_blue Turns on the blue chamber light when enabled (1)\n# TYPE plc_chamber_light_blue gauge\nplc_chamber_light_blue{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x0007\",value_type=\"bool\"} 1.0\n# HELP plc_chamber_light_purple Turns on the purple chamber light when enabled (1)\n# TYPE plc_chamber_light_purple gauge\nplc_chamber_light_purple{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x0009\",value_type=\"bool\"} 1.0\n# HELP plc_crane_motor_power Starts the crane motor when enabled (1)\n# TYPE plc_crane_motor_power gauge\nplc_crane_motor_power{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x000d\",value_type=\"bool\"} 1.0\n# HELP plc_crane_motor_direction_left Sets the crane motor direction to left when enabled (1)\n# TYPE plc_crane_motor_direction_left gauge\nplc_crane_motor_direction_left{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x000e\",value_type=\"bool\"} 0.0\n# HELP plc_crane_motor_direction_right Sets the crane motor direction to right when enabled (1)\n# TYPE plc_crane_motor_direction_right gauge\nplc_crane_motor_direction_right{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"coils\",start_address=\"0x000f\",value_type=\"bool\"} 1.0\n# HELP plc_emergency_stop Indicates if the emergency stop button is active\n# TYPE plc_emergency_stop gauge\nplc_emergency_stop{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"discrete_inputs\",start_address=\"0x0004\",value_type=\"bool\"} 1.0\n# HELP plc_crane_lift_at_horizontal_max Indicates if the crane is at the horizontal maximum position (right)\n# TYPE plc_crane_lift_at_horizontal_max gauge\nplc_crane_lift_at_horizontal_max{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"discrete_inputs\",start_address=\"0x0005\",value_type=\"bool\"} 1.0\n# HELP plc_crane_lift_at_horizontal_min Indicates if the crane is at the horizontal minimum position (left)\n# TYPE plc_crane_lift_at_horizontal_min gauge\nplc_crane_lift_at_horizontal_min{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"discrete_inputs\",start_address=\"0x0006\",value_type=\"bool\"} 0.0\n# HELP plc_temperature The temperature reported by the sensor\n# TYPE plc_temperature gauge\nplc_temperature{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"input_registers\",start_address=\"0x0001\",value_type=\"uint16\"} 254.0\n# HELP plc_material_name Testing string\n# TYPE plc_material_name gauge\nplc_material_name{index=\"0\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"input_registers\",start_address=\"0x0003\",value_type=\"ascii\"} 70.0\nplc_material_name{index=\"1\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"input_registers\",start_address=\"0x0003\",value_type=\"ascii\"} 101.0\n# HELP plc_material_scalar Testing float\n# TYPE plc_material_scalar gauge\nplc_material_scalar{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"input_registers\",start_address=\"0x0005\",value_type=\"float32\"} 3.14\n# HELP plc_target_temperature The target temperature to set the heating element to\n# TYPE plc_target_temperature gauge\nplc_target_temperature{index=\"None\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"holding_registers\",start_address=\"0x0001\",value_type=\"uint16\"} 870.0\n# HELP plc_test_string Testing string\n# TYPE plc_test_string gauge\nplc_test_string{index=\"0\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"holding_registers\",start_address=\"0x0003\",value_type=\"ascii\"} 84.0\nplc_test_string{index=\"1\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"holding_registers\",start_address=\"0x0003\",value_type=\"ascii\"} 69.0\nplc_test_string{index=\"2\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"holding_registers\",start_address=\"0x0004\",value_type=\"ascii\"} 83.0\nplc_test_string{index=\"3\",manufacturer=\"test\",model=\"test-2000\",plc=\"master\",register_type=\"holding_registers\",start_address=\"0x0004\",value_type=\"ascii\"} 84.0\n```\n\n## Grafana dashboard\n\nThe exporter comes with a default dashboard which requires the following configuration:\n\n- metric_layout: `dynamic`\n- namespace: `plc`\n\nThe dashboard source can be found [here](dev/grafana/provisioning/dashboards/definitions/plc_exporter.json). You can copy the JSON file and import it into Grafana instance.\n\n![](https://raw.githubusercontent.com/sourcehawk/plc-exporter/refs/heads/master/doc/images/plc_exporter_dashboard_01.png)\n![](https://raw.githubusercontent.com/sourcehawk/plc-exporter/refs/heads/master/doc/images/plc_exporter_dashboard_02.png)\n\n## Development\n\n### Project setup\n\n1. (Optional) Set up virtual environment\n\n   ```bash\n   python3 -m venv venv\n   source venv/bin/activate\n   ```\n\n2. Install the exporter\n\n   ```bash\n   pip install -e .\n   ```\n\n3. Run the exporter\n\n   ```bash\n   plc_exporter --config /path/to/config.yaml\n   ```\n\n### Grafana dashboard and alert development\n\nA generic PCL Grafana dashboard is included in the [dev/grafana](./dev/grafana/provisioning/dashboards/definitions/) directory.\n\nTo develop the dashboard, or to develop a new dashboard for a specific PLC configuration, you can use the docker-compose setup.\n\n```bash\ndocker compose up --build\n```\n\nNote that to change the exporter configuration used, you can place your new configuration in the [examples directory](examples/) and change the command in the `plc_exporter` service in the `docker-compose.yml` file.\n\n```yaml\nplc_exporter:\n  hostname: plc-exporter\n  build:\n    context: .\n    dockerfile: Dockerfile\n  container_name: exporter\n  network_mode: host\n  # ports:\n  #   - \"9075:9075\"\n  volumes:\n    - ./examples:/configs\n  command:\n    - --config=/configs/your-custom-config.yaml # \u003c-- CHANGED THIS LINE\n```\n\n- Grafana will be available at [http://localhost:3000](http://localhost:3000) with the default credentials `admin:admin`.\n- Prometheus will be available at [http://localhost:9090](http://localhost:9090).\n- Alertmanager will be available at [http://localhost:9093](http://localhost:9093).\n\nNote that while developing dashboards it is a good idea to save it regularly to a JSON file in the [dashboard definitions directory](./dev/grafana/provisioning/dashboards/definitions/). This way the dashboard wont get lost and can be version controlled and shared with others.\n\n### PLC access\n\nTo access a PLC for development, you can use SSH tunneling to forward the PLC port to your local machine. This is done by adding a `LocalForward` line to your SSH config file (usually located at `~/.ssh/config`).\n\nFor example, if you want to access a PLC with IP `10.0.0.10` on port `502`, on a device you can add the following line to your SSH config file:\n\n```txt\nLocalForward 1502 10.0.0.10:502\n```\n\nThen you can develop your metrics by connecting to the PLC on port 1502 from your local machine (localhost).\n\n```txt\nHost some-remote-host\n    Hostname 168.0.0.10\n    User ubuntu\n    LocalForward 1502 10.0.0.10:502\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourcehawk%2Fplc-exporter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsourcehawk%2Fplc-exporter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourcehawk%2Fplc-exporter/lists"}