Migrating from 1.x? See
CHANGELOG.md— v2.0.0 drops Node ≤ 18, replacesaxioswith nativefetch, removes deep imports, and adds DMX512 support. Migration snippets are at the bottom of the changelog.
- Introduction
- Installation
- Examples
- Error handling
- Full API docs
- A brief description of the ProCon.IP pool controller
- Changelog
- Disclaimer
procon-ip is a TypeScript client library for the ProCon.IP pool controller.
It wraps the controller's small HTTP surface (/GetState.csv, /usrcfg.cgi,
/Command.htm, /SetState.pl, /GetDmx.csv) in typed services with a clean
error model and a read-mutate-write idiom for DMX512.
Feel free to ask questions on GitHub Issues — discussions there are searchable for everyone with a similar question.
Requires Node 22 LTS or newer.
pnpm add procon-ip
# or
npm install procon-ip
# or
yarn add procon-ipThe package ships dual ESM + CJS via the exports map — both
import { GetStateService } from 'procon-ip' and
const { GetStateService } = require('procon-ip') work.
The runnable forms of the snippets below live under
examples/.
import { GetStateService, GetStateCategory, Logger } from 'procon-ip';
const logger = new Logger();
const config = {
controllerUrl: 'http://192.168.2.3', // <-- your pool controller's address
basicAuth: true,
username: 'admin',
password: 'admin',
updateInterval: 5000,
timeout: 5000,
errorTolerance: 2,
};
const dataSource = new GetStateService(config, logger);
// One-shot fetch:
const data = await dataSource.update();
logger.info(`Uptime: ${data.sysInfo.uptime}`);
// Or poll continuously:
dataSource.start((data) => {
for (const obj of data.getDataObjectsByCategory(GetStateCategory.ELECTRODES)) {
logger.info(`${obj.label}: ${obj.displayValue}`);
}
});import {
GetStateService,
UsrcfgCgiService,
RelayDataInterpreter,
GetStateCategory,
Logger,
} from 'procon-ip';
const logger = new Logger();
const config = {
controllerUrl: 'http://192.168.2.3',
basicAuth: true,
username: 'admin',
password: 'admin',
timeout: 5000,
updateInterval: 5000,
errorTolerance: 2,
};
const dataSource = new GetStateService(config, logger);
const interpreter = new RelayDataInterpreter(logger);
const relays = new UsrcfgCgiService(config, logger, dataSource, interpreter);
await dataSource.update();
// Switch a known dosage relay off:
await relays.setOff(dataSource.data.getChlorineDosageControl());
// Or hand a relay back to the controller's automatic schedule by label:
for (const relay of dataSource.data.getDataObjectsByCategory(GetStateCategory.RELAYS)) {
if (relay.label === 'Gartenlicht') await relays.setAuto(relay);
}setOn / setOff / setAuto return Promise<void> in v2 — failures throw
the typed error classes from 'procon-ip' instead of returning a numeric
response code (see Error handling).
import { CommandService, Logger } from 'procon-ip';
const svc = new CommandService(
{ controllerUrl: 'http://192.168.2.3', basicAuth: false, timeout: 5000 },
new Logger(),
);
const seconds = await svc.setChlorineDosage(60); // dose for 60s
// `seconds` === 60 on success, -1 after three retries.import { GetDmxService, DmxService, Logger } from 'procon-ip';
const logger = new Logger();
const config = { controllerUrl: 'http://192.168.2.3', basicAuth: false, timeout: 5000 };
const reader = new GetDmxService(config, logger);
const writer = new DmxService(config, logger);
const dmx = await reader.update();
for (const ch of dmx) dmx.set(ch.index, (ch.value + 64) % 256); // shift each channel
await writer.set(dmx);GetDmxData is iterable, indexable via at(index), mutable via
set(index, value) (clamps to [0, 255]), and produces the controller's
form payload via toPostData(). The controller only accepts full 16-channel
writes, so DmxService.set always sends every channel.
All HTTP calls throw typed errors from 'procon-ip':
import {
BadCredentialsError,
BadStatusCodeError,
RequestTimeoutError,
InvalidPayloadError,
ProconIpError,
} from 'procon-ip';
try {
await getStateService.update();
} catch (e) {
if (e instanceof BadCredentialsError) console.error('check basicAuth');
else if (e instanceof RequestTimeoutError) console.error(`timeout: ${e.timeoutMs}ms`);
else if (e instanceof BadStatusCodeError) console.error(`HTTP ${e.status}`);
else throw e;
}Network-level failures (DNS, connection refused, etc.) propagate as native
TypeError from fetch.
Auto-generated TypeDoc reference, deployed on every push to master: https://ylabonte.github.io/procon-ip/.
The ProCon.IP pool controller is a low-budget network-attached control unit for home swimming pools. With its software-switched relays it can drive multiple pumps (filter and dosage) either on a time schedule or based on sensor readings (i/o flow sensors, Dallas 1-Wire thermometers, redox / pH electrodes), and it can also switch arbitrary relays on demand for things like garden lighting. Its DMX512 extension drives 16 channels of lighting or other DMX-compatible gear.
Not all of its functionality is reachable via the documented HTTP API, so
this library reverse-engineers the parts the web UI uses (/usrcfg.cgi for
relay + DMX writes, /Command.htm for manual dosage, /SetState.pl for
relay timers).
For more information (German only):
See CHANGELOG.md.
I have nothing to do with the development, sales, marketing, or support of the pool controller unit itself. This is a community library that originated from my own ioBroker integration, later split out for reuse.

