#!/usr/bin/env python3 """Display format mask for every project item. Shared by both generators. CI Server's `` is a **digit mask**, not a Java DecimalFormat pattern: 99.99 -> two integer digits, always two decimals That is what CI View wrote when the FIT-201 INLET value was set to 99.99 by hand, and it matches the `VALUE_FORMAT` masks in the item export (`"99999"` on our items, `"99.99"` on the reference analog IO). `9` is a digit position; the count of positions after the point is the number of decimals always shown. The first build emitted `0` and `0.0` - DecimalFormat patterns, which read as masks **one digit wide**. Every value wider than that had nowhere to render. That, not the missing ``, is why the displays read 0: `WRPS_TagTest` carried no `` at all and all 98 bindings were live. So a mask must be **wide enough for the point's full range**, or the value may not fit. The masks below are sized from the register map's engineering ranges, and points of the same kind share a mask so the screens stay consistent: level & speed in %, and their setpoints 999.9 flows (m3/h, three pumps reach ~1300) 9999.9 durations & hour counters (s / h) 99999 volume (m3) 9999 alarm bitmask (unsigned 16-bit) 99999 enums, counts, modes, booleans 9 Decimals follow the point's real resolution, not a fixed two: level in % of the 6.000 m spill weir resolves to 0.017% because the register is mm, so one decimal is honest and two would not be; a flow in m3/h resolves to 0.1. Integer registers get no decimal at all - a decimal place there would be invented precision. **These masks must agree with `format_mask` in `04-scada/modbus_points/ci-server-points.csv`,** which is what the item export carries into CI Server as `VALUE_FORMAT`. The units and their gains are defined once, in `gen_scada_points.py`. """ # item short name (after "AID.WRPS.") -> mask MASKS = { # --- digital status, one digit ------------------------------------ "PU301.RUN_CMD": "9", "PU302.RUN_CMD": "9", "PU303.RUN_CMD": "9", "PU301.RUNNING": "9", "PU302.RUNNING": "9", "PU303.RUNNING": "9", "PU301.AVAILABLE": "9", "PU302.AVAILABLE": "9", "PU303.AVAILABLE": "9", "STN.IN_AUTO": "9", "STN.HIGH_LEVEL": "9", "STN.SPILL_ACTIVE": "9", "PU301.TRIPPED": "9", "PU302.TRIPPED": "9", "PU303.TRIPPED": "9", # --- analog measurements ------------------------------------------ "STN.LEVEL": "999.9", # %, 100% = the 6.000 m spill weir "STN.INFLOW": "9999.9", # m3/h, raw (L/s x10) x 0.36 "STN.DISCHARGE": "9999.9", # m3/h, three pumps reach ~1300 "STN.PUMPS_RUNNING": "9", # count 0-3 "STN.SPEED": "999.9", # %, 100% = 50 Hz "STN.TIME_TO_SPILL": "99999", # s, 32767 = drawing down "STN.TIME_TO_LSHH": "99999", # s, 32767 = drawing down "STN.NET_ACCUM": "9999.9", # m3/h, SIGNED "PU301.RUN_HOURS": "99999", # h "PU302.RUN_HOURS": "99999", "PU303.RUN_HOURS": "99999", "STN.VOL_TO_SPILL": "9999", # m3 # --- enums and words ---------------------------------------------- "STN.STATE": "9", # enum 3.1 "PU301.STATE": "9", # enum 3.2 "PU302.STATE": "9", "PU303.STATE": "9", "STN.DUTY_PUMP": "9", # 0 = none, 1-3 "STN.ALARM_WORD": "99999", # unsigned 16-bit bitmask "STN.CMD_ACK": "99", # --- setpoints ------------------------------------------------------ "SP.MODE": "9", # 1 = auto, 2 = off "SP.CMD_WORD": "99", "SP.CMD_PARAM": "9", # pump number "SP.LEVEL_SP": "999.9", # %, entered in % of spill weir "SP.START_DUTY": "999.9", # % "SP.START_P2": "999.9", # % "SP.START_P3": "999.9", # % "SP.STOP_ALL": "999.9", # % "SP.HIGH_ALARM": "999.9", # % "SP.MIN_SPEED": "999.9", # %, 100% = 50 Hz "SP.SERVICE_HRS": "99999", # h # --- simulation ------------------------------------------------------ "SIM.INFLOW": "9999.9", # m3/h "SIM.SCENARIO": "9", # 0-3 "SIM.RESET": "9", # write 1, self-clearing "SIM.TIME_SCALE": "999", # 1-120 } PREFIX = "AID.WRPS." def mask(item): """Mask for an item, by full or short name. Unknown -> widest integer.""" short = item[len(PREFIX):] if item.startswith(PREFIX) else item return MASKS.get(short, "99999") def decimals(item): m = mask(item) return len(m.split(".")[1]) if "." in m else 0 def check(names): """Fail loudly if an item has no mask - a new point must be given one.""" missing = [n for n in names if (n[len(PREFIX):] if n.startswith(PREFIX) else n) not in MASKS] if missing: import sys sys.exit("no format mask for: %s\n add it to point_format.py" % ", ".join(missing))