	# Energy SCADA — dual-site compute pod campuses — paste under:  def handleTimerEvent():
	# Gateway: ignition-2  ·  Edge Node: Energy  ·  Group: Energy  ·  Timer: 1s
	# Tag root: [default]DC/Sites/{West|East}/...
	# Module analogs: AI-01 pods, M-01 MV, E-01 UPS, E-02 skid, DG-01 gens, G-03/G-06 glue, M-02 chillers
	# Prefer VPS simulation as primary demo; this timer mirrors site KPIs + pod loads.
	import math
	import random
	import system
	from java.util import Calendar

	PROVIDER = "default"
	ROOT = "DC"

	def tag_path(rel):
		return "[%s]%s/%s" % (PROVIDER, ROOT, rel)

	def _read(rel, default=None):
		try:
			q = system.tag.readBlocking([tag_path(rel)])[0]
			if q.quality.isGood() and q.value is not None:
				return q.value
		except:
			pass
		return default

	def _write_many(rels, values):
		if not rels:
			return
		try:
			system.tag.writeBlocking([tag_path(r) for r in rels], values)
		except:
			pass

	cal = Calendar.getInstance()
	hour = cal.get(Calendar.HOUR_OF_DAY) + cal.get(Calendar.MINUTE) / 60.0
	diurnal = 0.55 + 0.45 * (0.5 + 0.5 * math.sin((hour - 8) / 24.0 * 2 * math.pi))
	h = int(cal.get(Calendar.HOUR_OF_DAY))
	if h >= 6 and h < 14:
		shift = "Day"
	elif h >= 14 and h < 22:
		shift = "Swing"
	else:
		shift = "Night"

	sites = [
		{"id": "West", "pods": 10, "vkV": 34.5, "gens": 4},
		{"id": "East", "pods": 5, "vkV": 24.9, "gens": 3},
	]

	for site in sites:
		sid = site["id"]
		base = "Sites/%s" % sid
		sp = float(_read(base + "/Site/Cmd_ChillerSp_C", 7.0) or 7.0)
		leave = float(_read(base + "/Chiller/CH1/LeavingWater_C", 7.2) or 7.2)
		leave = leave + (sp - leave) * 0.08 + random.uniform(-0.05, 0.05)
		if bool(_read(base + "/Site/Cmd_ClearFault", False)):
			_write_many([base + "/Chiller/CH1/Fault", base + "/Site/Cmd_ClearFault"], [False, False])
		if bool(_read(base + "/Site/Cmd_AckAlarm", False)):
			_write_many([base + "/Alarms/CriticalCount", base + "/Alarms/HighCount", base + "/Alarms/ActiveText", base + "/Site/Cmd_AckAlarm"], [0, 0, "", False])
		exercise = bool(_read(base + "/Site/Cmd_ExerciseGen", False))
		stop_gen = bool(_read(base + "/Site/Cmd_StopGen", False))
		gen_run = bool(_read(base + "/Generator/G1/Running", False))
		gen_kw = float(_read(base + "/Generator/G1/kW", 0) or 0)
		if stop_gen:
			gen_run = False
			gen_kw = 0.0
			_write_many([base + "/Site/Cmd_StopGen"], [False])
		elif exercise:
			gen_run = True
			gen_kw = 750 + random.uniform(-40, 80)
			_write_many([base + "/Site/Cmd_ExerciseGen"], [False])
		elif gen_run and gen_kw > 0 and random.random() < 0.04:
			gen_run = False
			gen_kw = 0.0

		site_it = 0.0
		pods_online = 0
		rels = []
		vals = []
		for i in range(1, site["pods"] + 1):
			pid = "P%02d" % i
			p = "%s/Pods/%s" % (base, pid)
			phase = i * 0.37
			it = 95.0 + 55.0 * diurnal + 25.0 * math.sin(hour + phase) + random.uniform(-8, 8)
			if it < 75.0:
				it = 75.0
			if it > 200.0:
				it = 200.0
			fac = it * 1.22
			cold = 20.5 + (leave - 7.0) * 0.35 + random.uniform(-0.1, 0.15)
			hot = cold + 11.0 + it / 180.0
			hb = int(_read(p + "/Heartbeat", 0) or 0) + 1
			rels.extend([p + "/IT_kW", p + "/Facility_kW", p + "/Load_Pct", p + "/TrainsOnline", p + "/Running", p + "/Status", p + "/ColdAisle_C", p + "/HotAisle_C", p + "/CDU_Load_Pct", p + "/Fan_Pct", p + "/CommOk", p + "/Heartbeat"])
			vals.extend([round(it, 1), round(fac, 1), round(it / 2.0, 1), 4, True, "Online", round(cold, 2), round(hot, 2), round(30 + it / 3.5, 1), round(45 + it / 4.0, 1), True, hb])
			site_it += it
			pods_online += 1

		cooling_frac = 0.20 + 0.08 * math.sin(hour)
		facility_kw = site_it * (1.0 + cooling_frac) + random.uniform(-15, 15)
		pue = facility_kw / max(1.0, site_it)
		if pue < 1.15:
			pue = 1.15
		if pue > 1.42:
			pue = 1.42
		soc = float(_read(base + "/UPS/SOC_Pct", 98.5) or 98.5)
		on_batt = bool(_read(base + "/UPS/OnBattery", False))
		if on_batt:
			soc = max(20.0, soc - 0.05)
			ups_mode = "Battery"
		else:
			soc = min(99.5, soc + 0.01)
			ups_mode = "Online"
		fault = bool(_read(base + "/Chiller/CH1/Fault", False))
		hb = int(_read(base + "/Site/Heartbeat", 0) or 0) + 1
		enter = leave + 4.8 + site_it / 4000.0
		alarm_text = ""
		crit = 1 if fault else 0
		high = 1 if on_batt else 0
		if fault:
			alarm_text = sid + " CH1 Maglev fault"
		elif on_batt:
			alarm_text = sid + " UPS on battery"

		rels.extend([
			base + "/Site/Heartbeat", base + "/Site/IT_kW", base + "/Site/Facility_kW", base + "/Site/PUE", base + "/Site/Shift", base + "/Site/PodsOnline", base + "/Site/Status",
			base + "/MV/Voltage_kV", base + "/MV/Load_MW", base + "/MV/Status", base + "/MV/FeedersClosed_Count",
			base + "/UPS/Mode", base + "/UPS/SOC_Pct", base + "/UPS/Load_Pct", base + "/UPS/Runtime_Min", base + "/UPS/OnBattery",
			base + "/PowerSkid/Oil_C", base + "/PowerSkid/LV_Bus_V", base + "/PowerSkid/LV_Bus_A", base + "/PowerSkid/Load_kW",
			base + "/Generator/G1/Running", base + "/Generator/G1/kW",
			base + "/Chiller/CH1/LeavingWater_C", base + "/Chiller/CH1/EnteringWater_C", base + "/Chiller/CH1/Load_Pct", base + "/Chiller/CH1/Fault",
			base + "/CoolingGlue/Flow_Gpm", base + "/CoolingGlue/Supply_C", base + "/CoolingGlue/Return_C",
			base + "/ElectricalGlue/Bus_A", base + "/ElectricalGlue/InterconnectOk",
			base + "/Alarms/CriticalCount", base + "/Alarms/HighCount", base + "/Alarms/ActiveText",
			base + "/Health/MqttOk", base + "/Health/DevicesOnline",
		])
		vals.extend([
			hb, round(site_it, 1), round(facility_kw, 1), round(pue, 3), shift, pods_online, "Alarm" if fault else "Normal",
			round(site["vkV"] + random.uniform(-0.04, 0.04), 3), round(facility_kw / 1000.0, 3), "Normal", 10,
			ups_mode, round(soc, 2), round(max(15.0, site_it / 30.0), 1), round(max(5.0, soc / 5.5), 1), on_batt,
			round(55 + facility_kw / 250.0, 1), round(480 + random.uniform(-1.5, 1.5), 1), round(facility_kw * 1.25, 1), round(facility_kw * 0.94, 1),
			gen_run, round(gen_kw, 1),
			round(leave, 2), round(enter, 2), round(max(20.0, min(95.0, 35 + site_it / 40.0)), 1), fault,
			round((850.0 if sid == "West" else 420.0) * (0.85 + 0.2 * diurnal), 1), round(17.5 + (leave - 7.0) * 0.45, 2), round(27.0 + site_it / 500.0, 2),
			round(facility_kw * 2.4, 1), True,
			crit, high, alarm_text,
			True, (20 if sid == "West" else 14) - (1 if fault else 0),
		])
		_write_many(rels, vals)
