# Moxa Power Manager

## Overview

`moxa-power-manager` is a utility designed for Moxa industrial computers. It provides a comprehensive command-line interface (mx-power-mgmt) to manage system power modes, monitor input and backup power, inspect thermal sensors, and control CPU scaling governors. The tool communicates with a background daemon (mx-power-managerd) over a local Unix socket.

## Key Features

* **Power Mode Management (`system`)**
  * This feature is supported only on the **UC-3400A** series and may not be available on other product series.
    * Three Power modes: active (full performance), conservation (reduced CPU frequency), and standby (deep sleep).
    * Peripheral Power Control: Selectively disables peripherals (Wi-Fi, Cellular, USB, SD card, LEDs) per power mode.
    * Wakeup Source Management: Configures RTC timer and LAN Wake-on-LAN as system wakeup sources.
    * Service Control: Stops and restarts specified systemd services during mode transitions.
    * Custom Hooks: Executes user-defined suspend and resume scripts during mode transitions.
* **Input and Backup Power Monitoring (`power`)**
  * Monitors energy sources and enables graceful system shutdown during power loss.
* **Temperature Sensor Monitoring (`thermal`)**
  * Enumerates system thermal zones and reports current temperature, operational status, and physical sensor location.
* **Processor Frequency and Governor Control (`cpu`)**
  * Enumerates all CPU cores with cpufreq support, available governors, and frequencies.
  * Allows switching between available governors.

---

## Command-Line Interface Usage
The primary entry point is the `mx-power-mgmt` command. Below are a few common examples to get you started. For a complete list of commands, sub-commands, and detailed options, please refer to the built-in help menu.

### General Syntax

```bash
mx-power-mgmt <module> <action> [options]
```

**Power Mode Management**
```bash
# Show the current power mode and configuration
mx-power-mgmt system show
# Enable power-saving mode with reduced CPU frequency and auto-resume to Active mode after 3600 seconds
mx-power-mgmt system set-mode conservation -t 3600
# Enter deep sleep with RTC wake-up and auto-resume to Active mode after 3600 seconds
mx-power-mgmt system set-mode standby -t 3600
# Disable LAN1 as a wake-up source
mx-power-mgmt system set-wakeup-source LAN1 disable
# Disable RTC0 as a wake-up source (bypass anti-brick safety prompt)
mx-power-mgmt system set-wakeup-source RTC0 disable --force
```

**Input and Backup Power Monitoring**
```bash
# List all detected power sources
mx-power-mgmt power list
# Show details for a specific input and backup power source and output in JSON format
mx-power-mgmt power show PLP_DC_Input --json
# Enable system shutdown on power loss
mx-power-mgmt power set-auto-shutdown enabled
# Set the delay before shutdown begins after power loss
mx-power-mgmt power set-shutdown-delay 5
# Reset the State of Health (SOH) of the supercapacitor
mx-power-mgmt power reset-soh
```

**Temperature Sensor Monitoring**
```bash
# List all temperature sensors
mx-power-mgmt thermal list
# Show detailed information for CPU_Sensor1 and output in JSON format
mx-power-mgmt thermal show CPU_Sensor1 --json
```

**Processor Frequency and Governor Control**
```bash
# List all processor cores
mx-power-mgmt cpu list
# Show detailed parameters for Core0 and output in JSON format
mx-power-mgmt cpu show Core0 --json
# Set the CPU scaling governor for Core1 to powersave
mx-power-mgmt cpu set-governor powersave Core1
# Set the CPU scaling governor for all cores to powersave
mx-power-mgmt cpu set-governor powersave
```

### Backward Compatible Commands

`mx-pwr-mgmt` is a legacy compatibility wrapper retained for scripts written
against the previous version of this tool. It is supported **only on UC-3400A**.

> **Warning**: Migrate scripts to `mx-power-mgmt` as `mx-pwr-mgmt` will be removed in a future release.

**info**
```bash
# Show current power mode
mx-pwr-mgmt info
```

**set**
```bash
# Set power mode
mx-pwr-mgmt set --mode <active|conservation|standby>
# Set power mode with auto-resume timer
mx-pwr-mgmt set --mode conservation --second <sec>
mx-pwr-mgmt set --mode standby --second <sec>
```

**wakeupctl**
```bash
# List all wakeup sources
mx-pwr-mgmt wakeupctl
# Enable a wakeup source
mx-pwr-mgmt wakeupctl --enable <dev>
# Disable a wakeup source
mx-pwr-mgmt wakeupctl --disable <dev>
```

---

## API Usage
### D-Bus API

The daemon exposes asynchronous power events over the system D-Bus.

**Bus name**: `com.moxa.PowerManager`

**D-Bus policy** (`/etc/dbus-1/system.d/com.moxa.PowerManager.conf`):
- Only `root` may own the bus name.
- Any process may send messages to, or receive signals from, `com.moxa.PowerManager`.

#### Power Input — `com.moxa.PowerManager.PowerInput`

Object path: `/com/moxa/PowerManager/Power/<label>` (e.g. `/com/moxa/PowerManager/Power/PLP_DC_Input1`)

**Signal**: `StateChanged(u power_source, u plp_state)`

| Parameter | Type | Value |
|-----------|------|-------|
| `power_source` | uint32 | `0` = MAIN, `1` = SUPERCAP |
| `plp_state` | uint32 | `0` = IDLE, `1` = DELAYING, `2` = EXECUTING |

#### Supercapacitor — `com.moxa.PowerManager.SuperCap`

Object path: `/com/moxa/PowerManager/Power/<label>` (e.g. `/com/moxa/PowerManager/Power/PLP_Cap1`)

**Signal**: `StateChanged(u new_state, u prev_state, d esr_mohm, u cap_pct)`

| Parameter | Type | Value |
|-----------|------|-------|
| `new_state` | uint32 | `0` = CALCULATING, `1` = GOOD, `2` = WARNING, `3` = REPLACE, `4` = UNKNOWN |
| `prev_state` | uint32 | Same values as `new_state` |
| `esr_mohm` | double | Current ESR in mΩ |
| `cap_pct` | uint32 | Capacitance as % of initial capacity (0–100) |

#### Inspect with `busctl`

```bash
# List registered objects
busctl tree com.moxa.PowerManager

# Monitor all signals in real time
busctl monitor com.moxa.PowerManager

# Introspect a specific object
busctl introspect com.moxa.PowerManager /com/moxa/PowerManager/Power/PLP_DC_Input1
busctl introspect com.moxa.PowerManager /com/moxa/PowerManager/Power/PLP_Cap1
```

---

## Configuration & Customization
The daemon and its modules are customized via the /etc/moxa/moxa-power-manager/ directory.

* **Moxa Power Manager (`/etc/moxa/moxa-power-manager/common.conf`):**
  Global configuration shared across all models (e.g., log level).
* **Model Specific (`/etc/moxa/moxa-power-manager/model.conf.d/`):**
  Directory containing model-specific configuration files..
  * **UC-3400A.conf**
    * Power Modes: Define system behavior for active, conservation, and standby modes, including CPU governor and optional timers.
    * Peripheral Control: Specify peripherals to disable in each power mode.
    * Service Control: Specify systemd services to stop in specific power modes.
* **Custom Scripts (`/etc/moxa/moxa-power-manager/scripts/`):**
  * **Resume Hook (`custom-resume.sh`)**:
    * Runs when the system returns to active mode.
  * **Suspend Hook (`custom-suspend.sh`)**:
    * Runs before the system enters a power-saving mode.
  * **Shutdown Hook (`custom-shutdown.sh`)**:
    * Runs when a power loss condition persists for a defined duration and shutdown is triggered.

---

## Dependencies

### Build-time

| Package | Purpose |
|---------|---------|
| autoconf | Build system generation |
| automake | Makefile generation |
| gcc | C compiler |
| make | Build orchestration |
| pkg-config | Library metadata queries |
| libgpiod-dev (≥ 2.2) | GPIO control |
| libjson-c-dev | JSON serialization |
| libkmod-dev | Kernel module management |
| libsystemd-dev | D-Bus / systemd integration |
| libiniparser-dev | INI config file parsing |
| libcmocka-dev *(optional)* | Unit tests (`make check`) |
| valgrind *(optional)* | Memory analysis during tests |

### Run-time

| Package | Purpose |
|---------|---------|
| jq | JSON output formatting in CLI |
| moxa-version | Model detection |
| moxa-computer-interface-manager | MCIM D-Bus integration |

---

## Systemd Integration

The utility operates through a dedicated systemd service:

* `moxa-power-manager.service`: Brings up the Moxa Power Manager daemon

```bash
# Start the daemon
systemctl start moxa-power-manager

# Stop the daemon
systemctl stop moxa-power-manager

# Restart the daemon
systemctl restart moxa-power-manager

# Reload configuration without restarting (sends SIGHUP)
systemctl reload moxa-power-manager
```

---

## Building

### Native Build

**Prerequisites**

```bash
# Debian / Ubuntu — required
sudo apt-get install -y autoconf automake gcc make pkg-config \
    libgpiod-dev libjson-c-dev libkmod-dev libsystemd-dev libiniparser-dev

# Optional — required for running unit tests (make check)
sudo apt-get install -y libcmocka-dev
```

**Build**

```bash
./autogen.sh
./configure
make
```

### Debian Package (Docker)

The project provides a Docker-based build system that produces `.deb` packages
for both `amd64` and `arm64` without requiring any host dependencies beyond
Docker.

**Prerequisites**

**Linux**

```bash
# Debian / Ubuntu
sudo apt-get update && sudo apt-get install -y docker.io

# Add your user to the docker group (re-login to take effect)
sudo usermod -aG docker $USER
```

**macOS**

Install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/), or via Homebrew:
```bash
brew install --cask docker
```
Then launch Docker Desktop and wait for the engine to start.

**Windows**

1. Enable WSL 2:
   - Open PowerShell as Administrator and run:
     ```powershell
     wsl --install
     ```
   - Restart when prompted.
2. Install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/).
3. In Docker Desktop → Settings → General, ensure **Use the WSL 2 based engine** is checked.
4. Run `docker-build.sh` from a WSL 2 terminal (e.g. Ubuntu).

**Verify installation (all platforms)**

```bash
docker --version
```

**Build**

```bash
# Build .deb for both amd64 and arm64 (default)
./docker-build.sh

# Build for a specific architecture
./docker-build.sh amd64
./docker-build.sh arm64
```

Output packages are placed in `dist/`:

```plaintext
dist/
├── moxa-power-manager_<version>_amd64.deb
└── moxa-power-manager_<version>_arm64.deb
```

Unit tests (`make check`) run automatically during the amd64 build.
They are skipped for arm64 cross-compilation builds.

**How it works**

| File | Purpose |
|------|---------|
| `Dockerfile` | Debian trixie image with cross-compilation toolchain and test dependencies |
| `docker-build.sh` | Builds the Docker image and runs the container |
| `docker-entrypoint.sh` | Runs `dpkg-buildpackage` inside the container for each target architecture |

## Installation

```bash
make install
```

> **Note:** The scripts under `/etc/moxa/moxa-power-manager/scripts/` are made
> executable automatically during `apt` installation. If you install via
> `make install`, run the following manually:
> ```bash
> chmod +x /etc/moxa/moxa-power-manager/scripts/*
> ```

## Development

### Running Tests

Unit tests use [CMocka](https://cmocka.org/) and integrate with `make check`.
They run automatically as part of the Docker build (amd64 only).

To run locally:

```bash
./autogen.sh
./configure
make check
```

### Integration Tests

Integration tests run on the target device and validate both plain-text and
JSON output for all CLI commands. They require the daemon to be running.

**Prerequisites (on device)**

```bash
apt-get install -y jq
```

**Copy tests to device**

```bash
scp -r tests/integration/ <user>@<device>:/tmp/
```

**Run**

```bash
# Run all modules
/tmp/integration/run_tests.sh

# Specify binary path explicitly
/tmp/integration/run_tests.sh /usr/sbin/mx-power-mgmt

# Run with valgrind memory checks
/tmp/integration/run_tests.sh --valgrind

# Run a single module
/tmp/integration/test_help.sh
/tmp/integration/test_cpu.sh
/tmp/integration/test_thermal.sh
/tmp/integration/test_power.sh
/tmp/integration/test_system.sh
/tmp/integration/test_dbus.sh
/tmp/integration/test_backward_compat.sh
```

Each script prints per-test PASS/FAIL/SKIP and exits non-zero if any test fails.

### Adding a New Model

1. Create `src/models/<name>.c` and `include/models/<name>.h` with a `struct model_desc`.
2. Register the descriptor in `src/models/models.c` — add a header include and an entry to
   the `model_descs[]` array before the terminating `NULL`.
3. Add `data/model.conf.d/<Name>.conf` if the model requires model-specific configuration.

```c
/* include/models/my-model.h */
extern struct model_desc my_model_desc;

/* src/models/my-model.c */
#include "models/models.h"
#include "models/my-model.h"

static const char *const my_model_products[] = { "MY-PRODUCT-A", "MY-PRODUCT-B", NULL };

static int my_model_startup(const struct common_cfg *common) { ... }
static void my_model_shutdown(void) { ... }

struct model_desc my_model_desc = {
    .series        = "MY-SERIES",
    .product_names = my_model_products,
    .power_ctx     = &my_power_ctx,
    .cpu_ctx       = &my_cpu_ctx,
    .thermal_ctx   = &my_thermal_ctx,
    .startup       = my_model_startup,
    .shutdown      = my_model_shutdown,
};
```

```c
/* src/models/models.c — register the new descriptor */
#include "models/my-model.h"

static struct model_desc *model_descs[] = {
    ..., &my_model_desc, NULL,
};
```

### Code Style

```bash
clang-format -style=file -i ./src/*.c
```
