libelec
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
libelec.c
1 /*
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5  */
6 /*
7  * Copyright 2023 Saso Kiselkov. All rights reserved.
8  */
9 
10 #include <stdarg.h>
11 #include <stddef.h>
12 
13 #ifdef XPLANE
14 #include <XPLMDisplay.h>
15 #endif
16 
17 #include <acfutils/avl.h>
18 #include <acfutils/crc64.h>
19 #include <acfutils/math.h>
20 #include <acfutils/list.h>
21 #include <acfutils/perf.h>
22 #include <acfutils/safe_alloc.h>
23 #include <acfutils/worker.h>
24 
25 #ifdef LIBELEC_WITH_DRS
26 #include <acfutils/dr.h>
27 #endif
28 
29 #ifdef LIBELEC_WITH_LIBSWITCH
30 #include <libswitch.h>
31 #endif
32 
33 #ifdef LIBELEC_WITH_NETLINK
34 #include <netlink.h>
35 #endif
36 
37 #include "libelec.h"
38 #include "libelec_types_impl.h"
39 
40 #define EXEC_INTVAL 40000 /* us */
41 #define MAX_NETWORK_DEPTH 100 /* dimensionless */
42 #define NO_NEG_ZERO(x) ((x) == 0.0 ? 0.0 : (x))
43 #define CB_SW_ON_DELAY 0.33 /* sec */
44 #define MAX_COMPS (UINT16_MAX + 1)
45 #define GEN_MIN_RPM 1e-3
46 
47 #ifdef LIBELEC_WITH_NETLINK
48 
49 #define LIBELEC_NET_VERSION 1
50 #define NETMAPGET(map, idx) \
51  ((((map)[(idx) >> 3]) & (1 << ((idx) & 7))) != 0)
52 #define NETMAPSET(map, idx) \
53  do { \
54  (map)[(idx) >> 3] |= (1 << ((idx) & 7)); \
55  } while (0)
56 #define NETMAPSZ(sys) (list_count(&sys->comps) % 8 == 0 ? \
57  (list_count(&sys->comps) / 8) : (list_count(&sys->comps) / 8 + 1))
58 #define NETMAPSZ_REQ(sys) (sizeof (net_req_map_t) + NETMAPSZ(sys))
59 static bool send_net_recv_map(elec_sys_t *sys);
60 static void net_add_recv_comp(elec_comp_t *comp);
61 
62 #define NET_XMIT_INTVAL 5 /* divisor for 1 / EXEC_INTVAL */
63 #define NET_VOLTS_FACTOR 20.0 /* 0.05 V */
64 #define NET_AMPS_FACTOR 40.0 /* 0.025 A */
65 #define NET_FREQ_FACTOR 20.0 /* 0.05 Hz */
66 
67 #define NET_ADD_RECV_COMP(comp) net_add_recv_comp((elec_comp_t *)comp)
68 
69 /* #define LIBELEC_NET_DBG */
70 
71 #ifdef LIBELEC_NET_DBG
72 #define NET_DBG_LOG(...) logMsg(__VA_ARGS__)
73 #else
74 #define NET_DBG_LOG(...)
75 #endif
76 
77 #else /* !defined(LIBELEC_WITH_NETLINK) */
78 
79 #define NET_ADD_RECV_COMP(comp)
80 
81 #endif /* !defined(LIBELEC_WITH_NETLINK) */
82 
83 typedef struct {
84  bool pre;
85  elec_user_cb_t cb;
86  void *userinfo;
87  avl_node_t node;
89 
90 /*
91  * Can't use VECT2() and NULL_VECT2 macros here, MSVC doesn't have proper
92  * support for compound literals.
93  */
94 static const vect2_t batt_temp_energy_curve[] = {
95  {C2KELVIN(-90), 0.01},
96  {C2KELVIN(-75), 0.01},
97  {C2KELVIN(-50), 0.125},
98  {C2KELVIN(-20), 0.45},
99  {C2KELVIN(-5), 0.7},
100  {C2KELVIN(15), 0.925},
101  {C2KELVIN(40), 1.0},
102  {C2KELVIN(50), 1.0}
103 };
104 
105 static elec_comp_info_t *infos_parse(const char *filename, size_t *num_infos);
106 static void infos_free(elec_comp_info_t *infos, size_t num_infos);
107 
108 static bool_t elec_sys_worker(void *userinfo);
109 static void comp_free(elec_comp_t *comp);
110 static void network_paint_src_comp(elec_comp_t *src, elec_comp_t *upstream,
111  elec_comp_t *comp, unsigned depth);
112 static double network_load_integrate_comp(const elec_comp_t *src,
113  const elec_comp_t *upstream, elec_comp_t *comp, unsigned depth, double d_t);
114 static double network_load_integrate_load(const elec_comp_t *src,
115  elec_comp_t *comp, unsigned depth, double d_t);
116 
117 static double network_trace(const elec_comp_t *upstream,
118  const elec_comp_t *comp, unsigned depth, bool do_print);
119 
120 #ifdef LIBELEC_WITH_NETLINK
121 
122 static void netlink_send_msg_notif(netlink_conn_id_t conn_id, const void *buf,
123  size_t bufsz, void *userinfo);
124 static void netlink_recv_msg_notif(netlink_conn_id_t conn_id, const void *buf,
125  size_t bufsz, void *userinfo);
126 
127 static void elec_net_send_update(elec_sys_t *sys);
128 static void elec_net_recv_update(elec_sys_t *sys);
129 
130 #endif /* defined(LIBELEC_WITH_NETLINK) */
131 
132 #ifdef XPLANE
133 /*
134  * When X-Plane is in a modal UI window (such as the weather setup
135  * screen, or user settings) flight loop callbacks are never called.
136  * But window callbacks are, so we piggyback onto the Window draw
137  * callback to figure out if the sim is paused or not.
138  */
139 static int
140 elec_draw_cb(XPLMDrawingPhase phase, int before, void *refcon)
141 {
142  elec_sys_t *sys;
143  double sim_time, time_factor;
144 
145  UNUSED(phase);
146  UNUSED(before);
147  ASSERT(refcon != NULL);
148  sys = refcon;
149 
150  sim_time = dr_getf_prot(&sys->drs.sim_time);
151  /* Caution: sim_speed_act likes to contain a NAN while paused */
152  time_factor = round(dr_getf(&sys->drs.sim_speed_act) * 10) / 10;
153  if (sys->prev_sim_time >= sim_time || dr_geti(&sys->drs.replay) != 0 ||
154  dr_geti(&sys->drs.paused) != 0 || time_factor == 0) {
155  /* Reset the worker interval to default */
156  if (sys->started)
157  worker_set_interval_nowake(&sys->worker, EXEC_INTVAL);
158  mutex_enter(&sys->paused_lock);
159  sys->paused = true;
160  sys->time_factor = 0;
161  mutex_exit(&sys->paused_lock);
162  return (1);
163  }
164  sys->prev_sim_time = sim_time;
165  /*
166  * If the time factor returns to 1.0x, we want to make sure we
167  * go back to 1.0x exactly, instead of some fractional 1.04x thing
168  * in case the sim was only ever so slightly accelerated/decelerated.
169  */
170  if ((time_factor != sys->time_factor ||
171  (time_factor == 1 && sys->time_factor != 1)) && sys->started) {
172  worker_set_interval_nowake(&sys->worker,
173  EXEC_INTVAL / time_factor);
174  }
175  mutex_enter(&sys->paused_lock);
176  sys->paused = false;
177  sys->time_factor = time_factor;
178  mutex_exit(&sys->paused_lock);
179 
180  return (1);
181 }
182 #endif /* defined(XPLANE) */
183 
184 static inline bool
185 src_is_AC(const elec_comp_info_t *info)
186 {
187  ASSERT(info != NULL);
188  switch (info->type) {
189  case ELEC_GEN:
190  return (info->gen.freq != 0);
191  case ELEC_INV:
192  return (true);
193  case ELEC_XFRMR:
194  return (true);
195  default:
196  VERIFY_FAIL();
197  }
198 }
199 
200 static bool
201 check_upstream(const elec_comp_t *comp, const elec_comp_t *src,
202  const elec_comp_t *upstream)
203 {
204  ASSERT(comp != NULL);
205  ASSERT(src != NULL);
206  ASSERT3U(src->src_idx, <, ELEC_MAX_SRCS);
207  ASSERT(upstream != NULL);
208 
209  for (unsigned i = 0; i < comp->n_links; i++) {
210  if (comp->links[i].comp == upstream)
211  return (comp->links[i].srcs[src->src_idx] == src);
212  }
213  return (false);
214 }
215 
216 static double
217 get_src_fract(const elec_comp_t *comp, const elec_comp_t *src)
218 {
219  ASSERT(comp != NULL);
220  ASSERT(src != NULL);
221 
222  if (comp->src_int_cond_total > 1e-12) {
223  double src_cond = (1.0 / src->info->int_R) * src->rw.out_volts;
224  return (MIN(src_cond / comp->src_int_cond_total, 1));
225  } else {
226  return (1);
227  }
228 }
229 
230 static double
231 sum_link_amps(const elec_link_t *link)
232 {
233  double amps = 0;
234 
235  ASSERT(link != NULL);
236  for (unsigned i = 0; i < ELEC_MAX_SRCS; i++)
237  amps += link->out_amps[i];
238 
239  return (amps);
240 }
241 
242 static int
243 user_cb_info_compar(const void *a, const void *b)
244 {
245  const user_cb_info_t *ucbi_a = a, *ucbi_b = b;
246  const uintptr_t cb_a = (uintptr_t)ucbi_a->cb;
247  const uintptr_t cb_b = (uintptr_t)ucbi_a->cb;
248 
249  if (!ucbi_a->pre && ucbi_b->pre)
250  return (-1);
251  if (ucbi_a->pre && !ucbi_b->pre)
252  return (1);
253  if (cb_a < cb_b)
254  return (-1);
255  if (cb_a > cb_b)
256  return (1);
257  if (ucbi_a->userinfo < ucbi_b->userinfo)
258  return (-1);
259  if (ucbi_a->userinfo > ucbi_b->userinfo)
260  return (1);
261  return (0);
262 }
263 
264 static int
265 name2comp_compar(const void *a, const void *b)
266 {
267  const elec_comp_t *ca = a, *cb = b;
268  int res = strcmp(ca->info->name, cb->info->name);
269  if (res < 0)
270  return (-1);
271  if (res > 0)
272  return (1);
273  return (0);
274 }
275 
276 static int
277 info2comp_compar(const void *a, const void *b)
278 {
279  const elec_comp_t *ca = a, *cb = b;
280  if (ca->info < cb->info)
281  return (-1);
282  if (ca->info > cb->info)
283  return (1);
284  return (0);
285 }
286 
287 static elec_comp_t *
288 find_comp(elec_sys_t *sys, const elec_comp_info_t *info, const elec_comp_t *src)
289 {
290  elec_comp_t *comp;
291  const elec_comp_t srch = { .info = (elec_comp_info_t *)info };
292 
293  ASSERT(sys != NULL);
294  ASSERT(info != NULL);
295  ASSERT(src != NULL);
296 
297  comp = avl_find(&sys->info2comp, &srch, NULL);
298  ASSERT_MSG(comp != NULL, "Component for info %s not found "
299  "(referenced from %s)", srch.info->name, src->info->name);
300 
301  return (comp);
302 }
303 
304 static bool
305 resolve_bus_links(elec_sys_t *sys, elec_comp_t *bus)
306 {
307  ASSERT(sys != NULL);
308  ASSERT(bus != NULL);
309  ASSERT(bus->info != NULL);
310  ASSERT3U(bus->info->type, ==, ELEC_BUS);
311 
312  bus->n_links = bus->info->bus.n_comps;
313  bus->links = safe_calloc(bus->n_links, sizeof (*bus->links));
314 
315 #define CHECK_COMP(cond, reason) \
316  do { \
317  if (!(cond)) { \
318  logMsg("%s (%s:%d): %s", comp->info->name, \
319  sys->conf_filename, comp->info->parse_linenum, \
320  (reason)); \
321  return (false); \
322  } \
323  } while (0)
324 #define CHECK_COMP_V(cond, reason, ...) \
325  do { \
326  if (!(cond)) { \
327  logMsg("%s (%s:%d): " reason, comp->info->name, \
328  sys->conf_filename, comp->info->parse_linenum, \
329  __VA_ARGS__); \
330  return (false); \
331  } \
332  } while (0)
333 
334  for (size_t i = 0; i < bus->info->bus.n_comps; i++) {
335  elec_comp_t *comp = find_comp(sys, bus->info->bus.comps[i],
336  bus);
337 
338  bus->links[i].comp = comp;
339  ASSERT(comp->info != NULL);
340  switch (comp->info->type) {
341  case ELEC_BATT:
342  /* Batteries are DC-only devices! */
343  CHECK_COMP(!bus->info->bus.ac, "batteries cannot "
344  "connect to AC buses (batteries are inherently "
345  "DC-only devices)");
346  ASSERT3U(comp->n_links, ==, 1);
347  ASSERT(comp->links != NULL);
348  comp->links[0].comp = bus;
349  break;
350  case ELEC_GEN:
351  /* Generators can be DC or AC */
352  CHECK_COMP_V(bus->info->bus.ac == src_is_AC(comp->info),
353  "AC/DC status is mismatched between the generator "
354  "and its output bus %s", bus->info->name);
355  ASSERT3U(comp->n_links, ==, 1);
356  ASSERT(comp->links != NULL);
357  comp->links[0].comp = bus;
358  break;
359  case ELEC_TRU:
360  ASSERT3U(comp->n_links, ==, 2);
361  ASSERT(comp->links != NULL);
362  if (comp->info->tru.ac == bus->info) {
363  CHECK_COMP_V(bus->info->bus.ac, "input to the "
364  "TRU must connect to an AC bus, but "
365  "%s is DC", bus->info->name);
366  comp->links[0].comp = bus;
367  } else {
368  ASSERT3P(comp->info->tru.dc, ==, bus->info);
369  CHECK_COMP_V(!bus->info->bus.ac, "output of "
370  "the TRU must connect to a DC bus, "
371  "but %s is AC", bus->info->name);
372  comp->links[1].comp = bus;
373  }
374  break;
375  case ELEC_INV:
376  ASSERT3U(comp->n_links, ==, 2);
377  ASSERT(comp->links != NULL);
378  if (comp->info->tru.dc == bus->info) {
379  CHECK_COMP_V(!bus->info->bus.ac, "input to "
380  "the inverter must connect to a DC bus, "
381  "but %s is AC", bus->info->name);
382  comp->links[0].comp = bus;
383  } else {
384  ASSERT3P(comp->info->tru.ac, ==, bus->info);
385  CHECK_COMP_V(bus->info->bus.ac, "output of "
386  "the inverter must connect to an AC bus, "
387  "but %s is DC", bus->info->name);
388  comp->links[1].comp = bus;
389  }
390  break;
391  case ELEC_XFRMR:
392  ASSERT3U(comp->n_links, ==, 2);
393  ASSERT(comp->links != NULL);
394  if (comp->info->xfrmr.input == bus->info) {
395  CHECK_COMP_V(bus->info->bus.ac, "input to "
396  "the transformer must connect to an AC "
397  "bus, but %s is DC", bus->info->name);
398  comp->links[0].comp = bus;
399  } else {
400  ASSERT3P(comp->info->xfrmr.output, ==,
401  bus->info);
402  CHECK_COMP_V(bus->info->bus.ac, "output of "
403  "the transformer must connect to an "
404  "AC bus, but %s is DC", bus->info->name);
405  comp->links[1].comp = bus;
406  }
407  break;
408  case ELEC_LOAD:
409  CHECK_COMP_V(bus->info->bus.ac == comp->info->load.ac,
410  "cannot connect %s load to %s bus",
411  comp->info->load.ac ? "AC" : "DC",
412  bus->info->bus.ac ? "AC" : "DC");
413  ASSERT3U(comp->n_links, ==, 1);
414  ASSERT(comp->links != NULL);
415  comp->links[0].comp = bus;
416  break;
417  case ELEC_BUS:
418  CHECK_COMP_V(false, "Invalid link: cannot connect "
419  "bus %s directly to bus %s", bus->info->name,
420  comp->info->name);
421  break;
422  case ELEC_CB:
423  case ELEC_SHUNT:
424  /* 3-phase breakers are only allowed on AC buses */
425  if (comp->info->type == ELEC_CB) {
426  CHECK_COMP(!comp->info->cb.triphase ||
427  bus->info->bus.ac, "3-phase breakers "
428  "cannot be connected to DC buses");
429  }
430  ASSERT3U(comp->n_links, ==, 2);
431  ASSERT(comp->links != NULL);
432  if (comp->links[0].comp == NULL) {
433  comp->links[0].comp = bus;
434  } else {
435  elec_comp_t *other_bus = comp->links[0].comp;
436 
437  CHECK_COMP(comp->links[1].comp == NULL,
438  "too many connections");
439  comp->links[1].comp = bus;
440  CHECK_COMP_V(bus->info->bus.ac ==
441  other_bus->info->bus.ac, "cannot link "
442  "two buses of incompatible type (%s is "
443  "%s and %s is %s)",
444  bus->info->name,
445  bus->info->bus.ac ? "AC" : "DC",
446  other_bus->info->name,
447  other_bus->info->bus.ac ? "AC" : "DC");
448  }
449  break;
450  case ELEC_TIE:
451  comp->n_links++;
452  comp->links = safe_realloc(comp->links,
453  comp->n_links * sizeof (*comp->links));
454  comp->links[comp->n_links - 1].comp = bus;
455  free(comp->tie.cur_state);
456  comp->tie.cur_state = safe_calloc(comp->n_links,
457  sizeof (*comp->tie.cur_state));
458  free(comp->tie.wk_state);
459  comp->tie.wk_state = safe_calloc(comp->n_links,
460  sizeof (*comp->tie.wk_state));
461  break;
462  case ELEC_DIODE:
463  /*
464  * Diodes are DC-only devices. Don't attempt to build
465  * rectifiers, use a TRU for that!
466  */
467  CHECK_COMP_V(!bus->info->bus.ac, "cannot connect "
468  "diode %s to an AC bus (libelec cannot be used "
469  "to build a bridge rectifier, use a \"TRU\" "
470  "component for that)", comp->info->name);
471  ASSERT3U(comp->n_links, ==, 2);
472  ASSERT(comp->links != NULL);
473  if (comp->info->diode.sides[0] == bus->info) {
474  comp->links[0].comp = bus;
475  } else {
476  ASSERT3P(comp->info->diode.sides[1], ==,
477  bus->info);
478  comp->links[1].comp = bus;
479  }
480  break;
481  default:
482  VERIFY_FAIL();
483  }
484  }
485 #undef CHECK_COMP
486 #undef CHECK_COMP_V
487  return (true);
488 }
489 
490 static bool
491 resolve_comp_links(elec_sys_t *sys)
492 {
493  ASSERT(sys != NULL);
494 
495  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
496  comp = list_next(&sys->comps, comp)) {
497  if (comp->info->type == ELEC_BUS) {
498  if (!resolve_bus_links(sys, comp))
499  return (false);
500  } else if (comp->info->type == ELEC_TRU &&
501  comp->info->tru.charger) {
502  comp->tru.batt = find_comp(sys,
503  comp->info->tru.batt, comp);
504  comp->tru.batt_conn = find_comp(sys,
505  comp->info->tru.batt_conn, comp);
506  }
507  }
508  return (true);
509 }
510 
511 WARN_UNUSED_RES_ATTR static bool
512 check_comp_links(elec_sys_t *sys)
513 {
514  ASSERT(sys != NULL);
515 
516  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
517  comp = list_next(&sys->comps, comp)) {
518  ASSERT(comp->info != NULL);
519  for (unsigned i = 0; i < comp->n_links; i++) {
520  if (comp->links[i].comp == NULL) {
521  logMsg("Component %s is missing a network link",
522  comp->info->name);
523  return (false);
524  }
525  }
526  }
527  return (true);
528 }
529 
538 const elec_comp_info_t *
539 libelec_get_comp_infos(const elec_sys_t *sys, size_t *num_infos)
540 {
541  ASSERT(sys != NULL);
542  ASSERT(num_infos != NULL);
543  *num_infos = sys->num_infos;
544  return (sys->comp_infos);
545 }
546 
547 static bool
548 comp_alloc(elec_sys_t *sys, elec_comp_info_t *info, unsigned *src_i)
549 {
550  elec_comp_t *comp = safe_calloc(1, sizeof (*comp));
551  avl_index_t where;
552  ASSERT(info != NULL);
553 
554  comp->sys = sys;
555  comp->info = info;
556  mutex_init(&comp->rw_ro_lock);
557  VERIFY_MSG(avl_find(&sys->info2comp, comp, &where) == NULL,
558  "Duplicate elec info usage %s", info->name);
559  avl_insert(&sys->info2comp, comp, where);
560  list_insert_tail(&sys->comps, comp);
561 
562  VERIFY_MSG(avl_find(&sys->name2comp, comp, &where) ==
563  NULL, "Duplicate info name %s", info->name);
564  avl_insert(&sys->name2comp, comp, where);
565  /*
566  * Initialize component fields and default values
567  */
568  switch (comp->info->type) {
569  case ELEC_LOAD:
570  comp->load.random_load_factor = 1;
571  break;
572  case ELEC_BATT:
573  comp->src_idx = *src_i;
574  (*src_i)++;
575  mutex_init(&comp->batt.lock);
576  comp->batt.chg_rel = 1.0;
577  comp->batt.T = C2KELVIN(15);
578  break;
579  case ELEC_GEN:
580  comp->src_idx = *src_i;
581  (*src_i)++;
582  mutex_init(&comp->gen.lock);
583  comp->gen.tgt_volts = comp->info->gen.volts;
584  comp->gen.tgt_freq = comp->info->gen.freq;
585  comp->gen.ctr_rpm = AVG(comp->info->gen.min_rpm,
586  comp->info->gen.max_rpm);
587  comp->gen.rpm = GEN_MIN_RPM;
588  comp->gen.max_stab_U =
589  comp->gen.ctr_rpm / comp->info->gen.min_rpm;
590  comp->gen.min_stab_U =
591  comp->gen.ctr_rpm / comp->info->gen.max_rpm;
592  if (comp->info->gen.stab_rate_f > 0) {
593  comp->gen.max_stab_f = comp->gen.ctr_rpm /
594  comp->info->gen.min_rpm;
595  comp->gen.min_stab_f = comp->gen.ctr_rpm /
596  comp->info->gen.max_rpm;
597  }
598  break;
599  case ELEC_TRU:
600  case ELEC_INV:
601  /* TRUs and inverters are basically the same thing in libelec */
602  comp->src_idx = *src_i;
603  (*src_i)++;
604  break;
605  case ELEC_XFRMR:
606  comp->src_idx = *src_i;
607  (*src_i)++;
608  break;
609  case ELEC_BUS:
610  break;
611  case ELEC_CB:
612  case ELEC_SHUNT:
613  /* CBs and shunts are basically the same thing in libelec */
614  comp->scb.cur_set = comp->scb.wk_set = true;
615  break;
616  case ELEC_TIE:
617  mutex_init(&comp->tie.lock);
618  break;
619  case ELEC_DIODE:
620  break;
621  case ELEC_LABEL_BOX:
622  break;
623  }
624  /*
625  * Initialize links
626  */
627  switch (comp->info->type) {
628  case ELEC_BATT:
629  case ELEC_GEN:
630  case ELEC_LOAD:
631  comp->n_links = 1;
632  break;
633  case ELEC_TRU:
634  case ELEC_INV:
635  case ELEC_XFRMR:
636  case ELEC_CB:
637  case ELEC_SHUNT:
638  case ELEC_DIODE:
639  comp->n_links = 2;
640  break;
641  case ELEC_BUS:
642  case ELEC_LABEL_BOX:
643  case ELEC_TIE:
644  break;
645  }
646  if (comp->n_links != 0)
647  comp->links = safe_calloc(comp->n_links, sizeof (*comp->links));
648  /*
649  * Check if we're trying to create too many sources
650  */
651  if (comp->src_idx > ELEC_MAX_SRCS) {
652  logMsg("%s:%d: too many electrical sources (max: 64).",
653  sys->conf_filename, info->parse_linenum);
654  comp_free(comp);
655  return (false);
656  }
657  /*
658  * Insert the component into the relevant type-specific lists
659  */
660  if (comp->info->type == ELEC_BATT || comp->info->type == ELEC_GEN)
661  list_insert_tail(&sys->gens_batts, comp);
662  else if (comp->info->type == ELEC_TIE)
663  list_insert_tail(&sys->ties, comp);
664  /*
665  * If dataref exposing is enabled, create those now.
666  */
667 #ifdef LIBELEC_WITH_DRS
668  dr_create_f64(&comp->drs.in_volts, &comp->ro.in_volts,
669  false, "libelec/comp/%s/in_volts", comp->info->name);
670  dr_create_f64(&comp->drs.out_volts, &comp->ro.out_volts,
671  false, "libelec/comp/%s/out_volts", comp->info->name);
672  dr_create_f64(&comp->drs.in_amps, &comp->ro.in_amps,
673  false, "libelec/comp/%s/in_amps", comp->info->name);
674  dr_create_f64(&comp->drs.out_amps, &comp->ro.out_amps,
675  false, "libelec/comp/%s/out_amps", comp->info->name);
676  dr_create_f64(&comp->drs.in_pwr, &comp->ro.in_pwr,
677  false, "libelec/comp/%s/in_pwr", comp->info->name);
678  dr_create_f64(&comp->drs.out_pwr, &comp->ro.out_pwr,
679  false, "libelec/comp/%s/out_pwr", comp->info->name);
680 #endif /* defined(LIBELEC_WITH_DRS) */
681 
682  return (true);
683 }
684 
736 elec_sys_t *
737 libelec_new(const char *filename)
738 {
739  elec_sys_t *sys = safe_calloc(1, sizeof (*sys));
740  unsigned src_i = 0, comp_i = 0;
741  void *buf;
742  size_t bufsz;
743 
744  ASSERT(filename != NULL);
745 
746  buf = file2buf(filename, &bufsz);
747  if (buf == NULL) {
748  logMsg("Can't open %s: %s", filename, strerror(errno));
749  ZERO_FREE(sys);
750  return (NULL);
751  }
752  sys->conf_filename = safe_strdup(filename);
753  sys->conf_crc = crc64(buf, bufsz);
754  free(buf);
755 
756  sys->comp_infos = infos_parse(filename, &sys->num_infos);
757  if (sys->comp_infos == NULL) {
758  ZERO_FREE(sys);
759  return (NULL);
760  }
761  list_create(&sys->comps, sizeof (elec_comp_t),
762  offsetof(elec_comp_t, comps_node));
763  list_create(&sys->gens_batts, sizeof (elec_comp_t),
764  offsetof(elec_comp_t, gens_batts_node));
765  list_create(&sys->ties, sizeof (elec_comp_t),
766  offsetof(elec_comp_t, ties_node));
767  avl_create(&sys->info2comp, info2comp_compar, sizeof (elec_comp_t),
768  offsetof(elec_comp_t, info2comp_node));
769  avl_create(&sys->name2comp, name2comp_compar, sizeof (elec_comp_t),
770  offsetof(elec_comp_t, name2comp_node));
771 
772  mutex_init(&sys->user_cbs_lock);
773  avl_create(&sys->user_cbs, user_cb_info_compar,
774  sizeof (user_cb_info_t), offsetof(user_cb_info_t, node));
775  mutex_init(&sys->worker_interlock);
776  mutex_init(&sys->paused_lock);
777  sys->time_factor = 1;
778 
779  for (size_t i = 0; i < sys->num_infos; i++) {
780  if (!comp_alloc(sys, &sys->comp_infos[i], &src_i))
781  goto errout;
782  }
783  /* Resolve component links */
784  if (!resolve_comp_links(sys) || !check_comp_links(sys))
785  goto errout;
786  /*
787  * Network sending is using 16-bit indices
788  */
789  ASSERT3U(list_count(&sys->comps), <=, MAX_COMPS);
790  sys->comps_array = safe_calloc(list_count(&sys->comps),
791  sizeof (*sys->comps_array));
792  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
793  comp = list_next(&sys->comps, comp)) {
794  sys->comps_array[comp_i] = comp;
795  comp->comp_idx = comp_i;
796  comp_i++;
797  }
798 #ifdef XPLANE
799  fdr_find(&sys->drs.sim_speed_act, "sim/time/sim_speed_actual");
800  fdr_find(&sys->drs.sim_time, "sim/time/total_running_time_sec");
801  fdr_find(&sys->drs.paused, "sim/time/paused");
802  fdr_find(&sys->drs.replay, "sim/time/is_in_replay");
803  VERIFY(XPLMRegisterDrawCallback(elec_draw_cb, xplm_Phase_Window,
804  0, sys));
805 #endif /* defined(XPLANE) */
806 
807  return (sys);
808 errout:
809  libelec_destroy(sys);
810  return (NULL);
811 }
812 
813 /*
814  * Validates the elec_comp_info_t's we've parsed out of the file.
815  * This needs to run after the parsing phase, but before the network
816  * is fully ready to operate. The user might want to set up info
817  * struct callbacks before this is done, so we shouldn't error out
818  * on missing mandatory callbacks. That check is done in
819  * validate_elec_comp_infos_start() just before the network is started.
820  */
821 static bool
822 validate_elec_comp_infos_parse(const elec_comp_info_t *infos, size_t n_infos,
823  const char *filename)
824 {
825  ASSERT(infos != NULL || n_infos == 0);
826  ASSERT(filename != NULL);
827 
828 #define CHECK_COMP(cond, reason) \
829  do { \
830  if (!(cond)) { \
831  logMsg("%s (%s:%d): %s", info->name, filename, \
832  info->parse_linenum, (reason)); \
833  return (false); \
834  } \
835  } while (0)
836 
837  for (size_t i = 0; i < n_infos; i++) {
838  const elec_comp_info_t *info = &infos[i];
839 
840  switch (info->type) {
841  case ELEC_BATT:
842  CHECK_COMP(info->batt.volts > 0,
843  "missing required \"VOLTS\" parameter");
844  CHECK_COMP(info->batt.max_pwr > 0,
845  "missing required \"MAX_PWR\" parameter");
846  CHECK_COMP(info->batt.capacity >= 0,
847  "missing required \"CAPACITY\" parameter");
848  CHECK_COMP(info->batt.chg_R > 0,
849  "missing required \"CHG_R\" parameter");
850  break;
851  case ELEC_GEN:
852  CHECK_COMP(info->gen.volts > 0,
853  "missing required \"VOLTS\" parameter");
854  CHECK_COMP(info->gen.exc_rpm <= info->gen.min_rpm,
855  "\"EXC_RPM\" parameter must be less than or "
856  "equal to \"MIN_RPM\"");
857  CHECK_COMP(info->gen.min_rpm > 0,
858  "missing required \"MIN_RPM\" parameter");
859  CHECK_COMP(info->gen.max_rpm > 0,
860  "missing required \"MAX_RPM\" parameter");
861  CHECK_COMP(info->gen.min_rpm < info->gen.max_rpm,
862  "\"MIN_RPM\" must be lower than \"MAX_RPM\"");
863  CHECK_COMP(info->gen.eff_curve != NULL, "generators "
864  "require at least two \"CURVEPT EFF\" parameters");
865  break;
866  case ELEC_TRU:
867  case ELEC_INV:
868  CHECK_COMP(info->tru.in_volts > 0,
869  "missing required \"IN_VOLTS\" parameter");
870  CHECK_COMP(info->tru.out_volts > 0,
871  "missing required \"OUT_VOLTS\" parameter");
872  if (info->type == ELEC_INV) {
873  CHECK_COMP(info->tru.out_freq > 0,
874  "missing required \"OUT_FREQ\" parameter");
875  }
876  CHECK_COMP(info->tru.eff_curve != NULL,
877  "at least two \"CURVEPT EFF\" parameters required");
878  CHECK_COMP(info->tru.ac != NULL,
879  "AC side not connected");
880  CHECK_COMP(info->tru.dc != NULL,
881  "DC side not connected");
882  break;
883  case ELEC_XFRMR:
884  CHECK_COMP(info->xfrmr.in_volts > 0,
885  "missing required \"IN_VOLTS\" parameter");
886  CHECK_COMP(info->xfrmr.out_volts > 0,
887  "missing required \"OUT_VOLTS\" parameter");
888  CHECK_COMP(info->xfrmr.input != NULL,
889  "input side not connected");
890  CHECK_COMP(info->xfrmr.output != NULL,
891  "output side not connected");
892  break;
893  case ELEC_LOAD:
894  /*
895  * Stabilized loads MUST declare a minimum voltage.
896  * Non-stabilized loads don't need to.
897  */
898  CHECK_COMP(info->load.min_volts > 0 ||
899  !info->load.stab, "loads must specify "
900  "a \"MIN_VOLTS\" when \"STAB\" is set to TRUE");
901  ASSERT3F(info->load.incap_C, >=, 0);
902  if (info->load.incap_C > 0)
903  ASSERT3F(info->load.incap_R, >, 0);
904  break;
905  case ELEC_BUS:
906  CHECK_COMP(info->bus.comps != NULL,
907  "buses must connect to at least 1 component");
908  break;
909  case ELEC_CB:
910  case ELEC_SHUNT:
911  case ELEC_TIE:
912  break;
913  case ELEC_DIODE:
914  CHECK_COMP(info->diode.sides[0] != NULL &&
915  info->diode.sides[1] != NULL,
916  "diodes need to have both end points connected");
917  break;
918  case ELEC_LABEL_BOX:
919  break;
920  }
921  }
922 
923 #undef CHECK_COMP
924 
925  return (true);
926 }
927 
935 bool
936 libelec_sys_is_started(const elec_sys_t *sys)
937 {
938  ASSERT(sys != NULL);
939  return (sys->started);
940 }
941 
950 bool
951 libelec_sys_can_start(const elec_sys_t *sys)
952 {
953  ASSERT(sys != NULL);
954  return (!sys->started);
955 }
956 
976 bool
977 libelec_sys_start(elec_sys_t *sys)
978 {
979  ASSERT(sys != NULL);
980 
981  if (!sys->started) {
982  if (!libelec_sys_can_start(sys))
983  return (false);
984 #ifndef LIBELEC_SLOW_DEBUG
985  worker_init(&sys->worker, elec_sys_worker, EXEC_INTVAL, sys,
986  "elec_sys");
987 #else /* !LIBELEC_SLOW_DEBUG */
988  worker_init(&sys->worker, elec_sys_worker, 0, sys, "elec_sys");
989 #endif /* !LIBELEC_SLOW_DEBUG */
990  sys->started = true;
991  }
992  return (true);
993 }
994 
1002 void
1003 libelec_sys_stop(elec_sys_t *sys)
1004 {
1005  ASSERT(sys != NULL);
1006 
1007  if (!sys->started)
1008  return;
1009 
1010  worker_fini(&sys->worker);
1011 #ifdef LIBELEC_WITH_NETLINK
1012  if (sys->net_recv.active) {
1013  memset(sys->net_recv.map, 0, NETMAPSZ(sys));
1014  send_net_recv_map(sys);
1015  }
1016 #endif /* defined(LIBELEC_WITH_NETLINK) */
1017  sys->started = false;
1018 }
1019 
1043 void
1044 libelec_sys_set_time_factor(elec_sys_t *sys, double time_factor)
1045 {
1046  ASSERT(sys != NULL);
1047  ASSERT3F(time_factor, >=, 0);
1048 #ifndef XPLANE
1049  if (time_factor == 0) {
1050  /* Reset the worker interval to default */
1051  if (sys->started)
1052  worker_set_interval_nowake(&sys->worker, EXEC_INTVAL);
1053  mutex_enter(&sys->paused_lock);
1054  sys->paused = true;
1055  sys->time_factor = 0;
1056  mutex_exit(&sys->paused_lock);
1057  return;
1058  }
1059  /*
1060  * If the time factor returns to 1.0x, we want to make sure we
1061  * go back to 1.0x exactly, instead of some fractional 1.04x thing
1062  * in case the sim was only ever so slightly accelerated/decelerated.
1063  */
1064  if ((fabs(time_factor - sys->time_factor) > 0.1 ||
1065  (time_factor == 1 && sys->time_factor != 1)) && sys->started) {
1066  worker_set_interval_nowake(&sys->worker,
1067  EXEC_INTVAL / time_factor);
1068  }
1069  mutex_enter(&sys->paused_lock);
1070  sys->paused = false;
1071  sys->time_factor = time_factor;
1072  mutex_exit(&sys->paused_lock);
1073 #endif /* !defined(XPLANE) */
1074 }
1075 
1080 double
1081 libelec_sys_get_time_factor(const elec_sys_t *sys)
1082 {
1083  ASSERT(sys != NULL);
1084  return (sys->time_factor);
1085 }
1086 
1087 static void
1088 elec_comp_serialize(elec_comp_t *comp, conf_t *ser, const char *prefix)
1089 {
1090  ASSERT(comp != NULL);
1091  ASSERT(comp->info != NULL);
1092  ASSERT(comp->info->name != NULL);
1093  ASSERT(ser != NULL);
1094  ASSERT(prefix != NULL);
1095 
1096  LIBELEC_SERIALIZE_DATA_V(comp, ser, "%s/%s/data",
1097  prefix, comp->info->name);
1098 
1099  switch (comp->info->type) {
1100  case ELEC_BATT:
1101  LIBELEC_SERIALIZE_DATA_V(&comp->batt, ser, "%s/%s/batt",
1102  prefix, comp->info->name);
1103  break;
1104  case ELEC_GEN:
1105  LIBELEC_SERIALIZE_DATA_V(&comp->batt, ser, "%s/%s/gen",
1106  prefix, comp->info->name);
1107  break;
1108  case ELEC_LOAD:
1109  LIBELEC_SERIALIZE_DATA_V(&comp->batt, ser, "%s/%s/load",
1110  prefix, comp->info->name);
1111  break;
1112  case ELEC_CB:
1113  LIBELEC_SERIALIZE_DATA_V(&comp->scb, ser, "%s/%s/cb",
1114  prefix, comp->info->name);
1115  break;
1116  case ELEC_SHUNT:
1117  /* Nothing to serialize for a shunt */
1118  break;
1119  case ELEC_TIE:
1120  mutex_enter(&comp->tie.lock);
1121  conf_set_data_v(ser, "%s/%s/cur_state", comp->tie.cur_state,
1122  comp->n_links * sizeof (*comp->tie.cur_state),
1123  prefix, comp->info->name);
1124  mutex_exit(&comp->tie.lock);
1125  break;
1126  default:
1127  break;
1128  }
1129 }
1130 
1131 static bool
1132 elec_comp_deserialize(elec_comp_t *comp, const conf_t *ser, const char *prefix)
1133 {
1134  ASSERT(comp != NULL);
1135  ASSERT(comp->info != NULL);
1136  ASSERT(comp->info->name != NULL);
1137  ASSERT(ser != NULL);
1138  ASSERT(prefix != NULL);
1139 
1140  LIBELEC_DESERIALIZE_DATA_V(comp, ser, "%s/%s/data",
1141  prefix, comp->info->name);
1142 
1143  switch (comp->info->type) {
1144  case ELEC_BATT:
1145  LIBELEC_DESERIALIZE_DATA_V(&comp->batt, ser, "%s/%s/batt",
1146  prefix, comp->info->name);
1147  break;
1148  case ELEC_GEN:
1149  LIBELEC_DESERIALIZE_DATA_V(&comp->batt, ser, "%s/%s/gen",
1150  prefix, comp->info->name);
1151  break;
1152  case ELEC_LOAD:
1153  LIBELEC_DESERIALIZE_DATA_V(&comp->batt, ser, "%s/%s/load",
1154  prefix, comp->info->name);
1155  break;
1156  case ELEC_CB:
1157  LIBELEC_DESERIALIZE_DATA_V(&comp->scb, ser, "%s/%s/cb",
1158  prefix, comp->info->name);
1159  break;
1160  case ELEC_SHUNT:
1161  /* Nothing to deserialize for a shunt */
1162  break;
1163  case ELEC_TIE:
1164  mutex_enter(&comp->tie.lock);
1165  if (conf_get_data_v(ser, "%s/%s/cur_state", comp->tie.cur_state,
1166  comp->n_links * sizeof (*comp->tie.cur_state),
1167  prefix, comp->info->name) !=
1168  comp->n_links * sizeof (*comp->tie.cur_state)) {
1169  mutex_exit(&comp->tie.lock);
1170  return (false);
1171  }
1172  mutex_exit(&comp->tie.lock);
1173  break;
1174  default:
1175  break;
1176  }
1177 
1178  return (true);
1179 }
1180 
1204 void
1205 libelec_serialize(elec_sys_t *sys, conf_t *ser, const char *prefix)
1206 {
1207  ASSERT(sys != NULL);
1208  ASSERT(ser != NULL);
1209  ASSERT(prefix != NULL);
1210 #ifdef LIBELEC_WITH_NETLINK
1211  ASSERT(!sys->net_recv.active);
1212 #endif
1213  conf_set_data_v(ser, "%s/conf_crc64", &sys->conf_crc,
1214  sizeof (sys->conf_crc), prefix);
1215 
1216  mutex_enter(&sys->worker_interlock);
1217 
1218  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
1219  comp = list_next(&sys->comps, comp)) {
1220  elec_comp_serialize(comp, ser, prefix);
1221  }
1222 
1223  mutex_exit(&sys->worker_interlock);
1224 }
1225 
1244 bool
1245 libelec_deserialize(elec_sys_t *sys, const conf_t *ser, const char *prefix)
1246 {
1247  uint64_t crc;
1248 
1249  ASSERT(sys != NULL);
1250  ASSERT(ser != NULL);
1251  ASSERT(prefix != NULL);
1252 #ifdef LIBELEC_WITH_NETLINK
1253  ASSERT(!sys->net_recv.active);
1254 #endif
1255  if (!conf_get_data_v(ser, "%s/conf_crc64", &crc, sizeof (crc),
1256  prefix)) {
1257  logMsg("Cannot deserialize libelec state: missing required "
1258  "state key %s/conf_crc64", prefix);
1259  return (false);
1260  }
1261  if (crc != sys->conf_crc) {
1262  logMsg("Cannot deserialize libelec state: configuration "
1263  "file CRC mismatch");
1264  return (false);
1265  }
1266  mutex_enter(&sys->worker_interlock);
1267 
1268  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
1269  comp = list_next(&sys->comps, comp)) {
1270  if (!elec_comp_deserialize(comp, ser, prefix)) {
1271  logMsg("Failed to deserialize %s: malformed state",
1272  comp->info->name);
1273  }
1274  }
1275 
1276  mutex_exit(&sys->worker_interlock);
1277 
1278  return (true);
1279 }
1280 
1286 void
1287 libelec_destroy(elec_sys_t *sys)
1288 {
1289  elec_comp_t *comp;
1290  user_cb_info_t *ucbi;
1291  void *cookie;
1292 
1293  ASSERT(sys != NULL);
1294 
1295  /* libelec_sys_stop MUST be called first! */
1296  ASSERT(!sys->started);
1297 
1298 #ifdef LIBELEC_WITH_NETLINK
1299  libelec_disable_net_send(sys);
1300  libelec_disable_net_recv(sys);
1301 #endif /* defined(LIBELEC_WITH_NETLINK) */
1302 
1303  cookie = NULL;
1304  while ((ucbi = avl_destroy_nodes(&sys->user_cbs, &cookie)) != NULL)
1305  free(ucbi);
1306  avl_destroy(&sys->user_cbs);
1307  mutex_destroy(&sys->user_cbs_lock);
1308 
1309  cookie = NULL;
1310  while (avl_destroy_nodes(&sys->info2comp, &cookie) != NULL)
1311  ;
1312  avl_destroy(&sys->info2comp);
1313 
1314  cookie = NULL;
1315  while (avl_destroy_nodes(&sys->name2comp, &cookie) != NULL)
1316  ;
1317  avl_destroy(&sys->info2comp);
1318 
1319  while (list_remove_head(&sys->gens_batts) != NULL)
1320  ;
1321  list_destroy(&sys->gens_batts);
1322 
1323  while (list_remove_head(&sys->ties) != NULL)
1324  ;
1325  list_destroy(&sys->ties);
1326 
1327  while ((comp = list_remove_head(&sys->comps)) != NULL)
1328  comp_free(comp);
1329  list_destroy(&sys->comps);
1330  free(sys->comps_array);
1331 
1332  mutex_destroy(&sys->worker_interlock);
1333  mutex_destroy(&sys->paused_lock);
1334 
1335  infos_free(sys->comp_infos, sys->num_infos);
1336 
1337  free(sys->conf_filename);
1338 #ifdef XPLANE
1339  (void)XPLMUnregisterDrawCallback(elec_draw_cb,
1340  xplm_Phase_Window, 0, sys);
1341 #endif
1342 
1343  memset(sys, 0, sizeof (*sys));
1344  free(sys);
1345 }
1346 
1347 #ifdef LIBELEC_WITH_LIBSWITCH
1348 
1349 void
1350 libelec_create_cb_switches(const elec_sys_t *sys, const char *prefix,
1351  float anim_rate)
1352 {
1353  ASSERT(sys != NULL);
1354  ASSERT(prefix != NULL);
1355 
1356  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
1357  comp = list_next(&sys->comps, comp)) {
1358  ASSERT(comp->info != NULL);
1359  if (comp->info->type == ELEC_CB) {
1360  char name[128], desc[128];
1361 
1362  VERIFY3S(snprintf(name, sizeof (name), "%s%s",
1363  prefix, comp->info->name), <, sizeof (name));
1364  VERIFY3S(snprintf(desc, sizeof (desc),
1365  "Circuit breaker %s", comp->info->name), <,
1366  sizeof (desc));
1367  comp->scb.sw = libswitch_add_toggle(name, desc,
1368  anim_rate);
1369  /* Invert the CB so '0' is popped and '1' is pushed */
1370  libswitch_set_anim_offset(comp->scb.sw, -1, 1);
1371  libswitch_set(comp->scb.sw, 0);
1372  libswitch_button_set_turn_on_delay(comp->scb.sw,
1373  CB_SW_ON_DELAY);
1374  }
1375  }
1376 }
1377 
1378 #endif /* defined(LIBELEC_WITH_LIBSWITCH) */
1379 
1380 #ifdef LIBELEC_SLOW_DEBUG
1381 
1382 void
1383 libelec_step(elec_sys_t *sys)
1384 {
1385  ASSERT(sys != NULL);
1386  worker_wake_up(&sys->worker);
1387 }
1388 
1389 #endif
1390 
1391 static const char*
1392 comp_type2str(elec_comp_type_t type)
1393 {
1394  switch (type) {
1395  case ELEC_BATT:
1396  return ("BATT");
1397  case ELEC_GEN:
1398  return ("GEN");
1399  case ELEC_TRU:
1400  return ("TRU");
1401  case ELEC_INV:
1402  return ("INV");
1403  case ELEC_XFRMR:
1404  return ("XFRMR");
1405  case ELEC_LOAD:
1406  return ("LOAD");
1407  case ELEC_BUS:
1408  return ("BUS");
1409  case ELEC_CB:
1410  return ("CB");
1411  case ELEC_SHUNT:
1412  return ("SHUNT");
1413  case ELEC_TIE:
1414  return ("TIE");
1415  case ELEC_DIODE:
1416  return ("DIODE");
1417  default:
1418  VERIFY(0);
1419  }
1420 }
1421 
1422 static bool
1423 add_info_link(elec_comp_info_t *info, elec_comp_info_t *info2,
1424  const char *slot_qual)
1425 {
1426  switch (info->type) {
1427  case ELEC_TRU:
1428  case ELEC_INV:
1429  if (slot_qual == NULL)
1430  return (false);
1431  if (strcmp(slot_qual, "AC") == 0) {
1432  if (info->tru.ac != NULL)
1433  return (false);
1434  info->tru.ac = info2;
1435  } else if (strcmp(slot_qual, "DC") == 0) {
1436  if (info->tru.dc != NULL)
1437  return (false);
1438  info->tru.dc = info2;
1439  } else {
1440  return (false);
1441  }
1442  break;
1443  case ELEC_XFRMR:
1444  if (slot_qual == NULL)
1445  return (false);
1446  if (strcmp(slot_qual, "IN") == 0) {
1447  if (info->xfrmr.input != NULL) {
1448  return (false);
1449  }
1450  info->xfrmr.input = info2;
1451  } else if (strcmp(slot_qual, "OUT") == 0) {
1452  if (info->xfrmr.output != NULL) {
1453  return (false);
1454  }
1455  info->xfrmr.output = info2;
1456  } else {
1457  return (false);
1458  }
1459  break;
1460  case ELEC_BUS:
1461  info->bus.comps = safe_realloc(info->bus.comps,
1462  (info->bus.n_comps + 1) * sizeof (*info->bus.comps));
1463  info->bus.comps[info->bus.n_comps] = info2;
1464  info->bus.n_comps++;
1465  break;
1466  case ELEC_DIODE:
1467  if (slot_qual == NULL)
1468  return (false);
1469  if (strcmp(slot_qual, "IN") == 0) {
1470  if (info->diode.sides[0] != NULL)
1471  return (false);
1472  info->diode.sides[0] = info2;
1473  } else {
1474  if (strcmp(slot_qual, "OUT") != 0)
1475  return (false);
1476  if (info->diode.sides[1] != NULL)
1477  return (false);
1478  info->diode.sides[1] = info2;
1479  }
1480  break;
1481  default:
1482  return(true);
1483  }
1484 
1485  return (true);
1486 }
1487 
1488 static elec_comp_info_t *
1489 find_comp_info(elec_comp_info_t *infos, size_t num_comps, const char *name)
1490 {
1491  for (unsigned i = 0; i < num_comps; i++) {
1492  if (infos[i].name != NULL && strcmp(infos[i].name, name) == 0)
1493  return (&infos[i]);
1494  }
1495  return (NULL);
1496 }
1497 
1498 static gui_load_type_t
1499 str2load_type(const char *str)
1500 {
1501  ASSERT(str != NULL);
1502  if (strcmp(str, "MOTOR") == 0)
1503  return (GUI_LOAD_MOTOR);
1504  return (GUI_LOAD_GENERIC);
1505 }
1506 
1507 static elec_comp_info_t *
1508 infos_parse(const char *filename, size_t *num_infos)
1509 {
1510 #define MAX_BUS_UNIQ 256
1511  uint64_t bus_IDs_seen[256] = { 0 };
1512  unsigned bus_ID_cur = 0;
1513  FILE *fp;
1514  size_t comp_i = 0, num_comps = 0;
1515  elec_comp_info_t *infos;
1516  elec_comp_info_t *info = NULL;
1517  char *line = NULL;
1518  size_t linecap = 0;
1519  unsigned linenum = 0;
1520 
1521  ASSERT(filename != NULL);
1522  ASSERT(num_infos != NULL);
1523 
1524  fp = fopen(filename, "r");
1525  if (fp == NULL) {
1526  logMsg("Can't open electrical network file %s: %s",
1527  filename, strerror(errno));
1528  return (NULL);
1529  }
1530 
1531  /* First count all components to know how many to allocate */
1532  while (parser_get_next_line(fp, &line, &linecap, &linenum) > 0) {
1533  if (strncmp(line, "BATT ", 5) == 0 ||
1534  strncmp(line, "GEN ", 4) == 0 ||
1535  strncmp(line, "TRU ", 4) == 0 ||
1536  strncmp(line, "INV ", 4) == 0 ||
1537  strncmp(line, "XFRMR ", 4) == 0 ||
1538  strncmp(line, "LOAD ", 5) == 0 ||
1539  strncmp(line, "BUS ", 4) == 0 ||
1540  strncmp(line, "CB ", 3) == 0 ||
1541  strncmp(line, "CB3 ", 4) == 0 ||
1542  strncmp(line, "SHUNT ", 6) == 0 ||
1543  strncmp(line, "TIE ", 4) == 0 ||
1544  strncmp(line, "DIODE ", 6) == 0 ||
1545  strncmp(line, "LABEL_BOX ", 10) == 0) {
1546  num_comps++;
1547  } else if (strncmp(line, "LOADCB ", 7) == 0 ||
1548  strncmp(line, "LOADCB3 ", 8) == 0) {
1549  num_comps += 2;
1550  }
1551  }
1552  rewind(fp);
1553 
1554  infos = safe_calloc(num_comps, sizeof (*infos));
1555  for (size_t i = 0; i < num_comps; i++) {
1556  elec_comp_info_t *info = &infos[i];
1557  info->gui.pos = NULL_VECT2;
1558  }
1559 
1560  linenum = 0;
1561  while (parser_get_next_line(fp, &line, &linecap, &linenum) > 0) {
1562  char **comps;
1563  const char *cmd;
1564  size_t n_comps;
1565 
1566 #define INVALID_LINE_FOR_COMP_TYPE \
1567  do { \
1568  logMsg("%s:%d: invalid %s line for component of type %s", \
1569  filename, linenum, cmd, comp_type2str(info->type)); \
1570  free_strlist(comps, n_comps); \
1571  goto errout; \
1572  } while (0)
1573 #define CHECK_DUP_NAME(__name__) \
1574  do { \
1575  elec_comp_info_t *info2 = find_comp_info(infos, comp_i, \
1576  (__name__)); \
1577  if (info2 != NULL) { \
1578  logMsg("%s:%d: duplicate component name %s " \
1579  "(previously found on line %d)", \
1580  filename, linenum, (__name__), \
1581  info2->parse_linenum); \
1582  free_strlist(comps, n_comps); \
1583  goto errout; \
1584  } \
1585  } while (0)
1586 #define CHECK_COMP(cond, reason) \
1587  do { \
1588  if (!(cond)) { \
1589  logMsg("%s:%d: %s", filename, linenum, (reason)); \
1590  free_strlist(comps, n_comps); \
1591  goto errout; \
1592  } \
1593  } while (0)
1594 #define CHECK_COMP_V(cond, reason, ...) \
1595  do { \
1596  if (!(cond)) { \
1597  logMsg("%s:%d: " reason, filename, linenum, \
1598  __VA_ARGS__); \
1599  free_strlist(comps, n_comps); \
1600  goto errout; \
1601  } \
1602  } while (0)
1603 
1604  comps = strsplit(line, " ", true, &n_comps);
1605  cmd = comps[0];
1606  if (strcmp(cmd, "BATT") == 0 && n_comps == 2) {
1607  ASSERT3U(comp_i, <, num_comps);
1608  CHECK_DUP_NAME(comps[1]);
1609  info = &infos[comp_i++];
1610  info->parse_linenum = linenum;
1611  info->type = ELEC_BATT;
1612  info->name = safe_strdup(comps[1]);
1613  info->int_R = 1;
1614  } else if (strcmp(cmd, "GEN") == 0 && n_comps == 2) {
1615  ASSERT3U(comp_i, <, num_comps);
1616  CHECK_DUP_NAME(comps[1]);
1617  info = &infos[comp_i++];
1618  info->parse_linenum = linenum;
1619  info->type = ELEC_GEN;
1620  info->name = safe_strdup(comps[1]);
1621  info->int_R = 1;
1622  } else if (strcmp(cmd, "TRU") == 0 && n_comps == 2) {
1623  ASSERT3U(comp_i, <, num_comps);
1624  CHECK_DUP_NAME(comps[1]);
1625  info = &infos[comp_i++];
1626  info->parse_linenum = linenum;
1627  info->type = ELEC_TRU;
1628  info->name = safe_strdup(comps[1]);
1629  info->int_R = 1;
1630  } else if (strcmp(cmd, "INV") == 0 && n_comps == 2) {
1631  ASSERT3U(comp_i, <, num_comps);
1632  CHECK_DUP_NAME(comps[1]);
1633  info = &infos[comp_i++];
1634  info->parse_linenum = linenum;
1635  info->type = ELEC_INV;
1636  info->name = safe_strdup(comps[1]);
1637  info->int_R = 1;
1638  } else if (strcmp(cmd, "XFRMR") == 0 && n_comps == 2) {
1639  ASSERT3U(comp_i, <, num_comps);
1640  CHECK_DUP_NAME(comps[1]);
1641  info = &infos[comp_i++];
1642  info->parse_linenum = linenum;
1643  info->type = ELEC_XFRMR;
1644  info->name = safe_strdup(comps[1]);
1645  info->int_R = 1;
1646  } else if (strcmp(cmd, "LOAD") == 0 &&
1647  (n_comps == 2 || n_comps == 3)) {
1648  ASSERT3U(comp_i, <, num_comps);
1649  CHECK_DUP_NAME(comps[1]);
1650  info = &infos[comp_i++];
1651  info->parse_linenum = linenum;
1652  info->type = ELEC_LOAD;
1653  info->name = safe_strdup(comps[1]);
1654  if (n_comps == 3)
1655  info->load.ac = (strcmp(comps[2], "AC") == 0);
1656  } else if (strcmp(cmd, "BUS") == 0 && n_comps == 3) {
1657  ASSERT3U(comp_i, <, num_comps);
1658  CHECK_DUP_NAME(comps[1]);
1659  info = &infos[comp_i++];
1660  info->parse_linenum = linenum;
1661  info->type = ELEC_BUS;
1662  info->name = safe_strdup(comps[1]);
1663  info->bus.ac = (strcmp(comps[2], "AC") == 0);
1664  memset(bus_IDs_seen, 0, sizeof (bus_IDs_seen));
1665  bus_ID_cur = 0;
1666  } else if ((strcmp(cmd, "CB") == 0 ||
1667  strcmp(cmd, "CB3") == 0) && n_comps == 3) {
1668  ASSERT3U(comp_i, <, num_comps);
1669  CHECK_DUP_NAME(comps[1]);
1670  info = &infos[comp_i++];
1671  info->parse_linenum = linenum;
1672  info->type = ELEC_CB;
1673  info->name = safe_strdup(comps[1]);
1674  info->cb.rate = 4;
1675  info->cb.max_amps = atof(comps[2]);
1676  info->cb.triphase = (strcmp(cmd, "CB3") == 0);
1677  } else if (strcmp(cmd, "SHUNT") == 0 && n_comps == 2) {
1678  ASSERT3U(comp_i, <, num_comps);
1679  CHECK_DUP_NAME(comps[1]);
1680  info = &infos[comp_i++];
1681  info->parse_linenum = linenum;
1682  info->type = ELEC_SHUNT;
1683  info->name = safe_strdup(comps[1]);
1684  } else if (strcmp(cmd, "TIE") == 0 && n_comps == 2) {
1685  ASSERT3U(comp_i, <, num_comps);
1686  CHECK_DUP_NAME(comps[1]);
1687  info = &infos[comp_i++];
1688  info->parse_linenum = linenum;
1689  info->type = ELEC_TIE;
1690  info->name = safe_strdup(comps[1]);
1691  } else if (strcmp(cmd, "DIODE") == 0 && n_comps == 2) {
1692  ASSERT3U(comp_i, <, num_comps);
1693  CHECK_DUP_NAME(comps[1]);
1694  info = &infos[comp_i++];
1695  info->parse_linenum = linenum;
1696  info->type = ELEC_DIODE;
1697  info->name = safe_strdup(comps[1]);
1698  } else if (strcmp(cmd, "LABEL_BOX") == 0 && n_comps >= 7) {
1699  size_t sz = 0;
1700  ASSERT3U(comp_i, <, num_comps);
1701  info = &infos[comp_i++];
1702  info->parse_linenum = linenum;
1703  info->type = ELEC_LABEL_BOX;
1704  info->label_box.pos =
1705  VECT2(atof(comps[1]), atof(comps[2]));
1706  info->label_box.sz =
1707  VECT2(atof(comps[3]), atof(comps[4]));
1708  info->label_box.font_scale = atof(comps[5]);
1709  for (size_t i = 6; i < n_comps; i++) {
1710  append_format(&info->name, &sz, "%s%s",
1711  comps[i], i + 1 < n_comps ? " " : "");
1712  }
1713  } else if (strcmp(cmd, "VOLTS") == 0 && n_comps == 2 &&
1714  info != NULL) {
1715  if (info->type == ELEC_BATT) {
1716  info->batt.volts = atof(comps[1]);
1717  CHECK_COMP(info->batt.volts > 0,
1718  "battery voltage must be positive");
1719  } else if (info->type == ELEC_GEN) {
1720  info->gen.volts = atof(comps[1]);
1721  CHECK_COMP(info->gen.volts > 0,
1722  "generator voltage must be positive");
1723  } else {
1724  INVALID_LINE_FOR_COMP_TYPE;
1725  }
1726  } else if (strcmp(cmd, "FREQ") == 0 && n_comps == 2 &&
1727  info != NULL && info->type == ELEC_GEN) {
1728  info->gen.freq = atof(comps[1]);
1729  CHECK_COMP(info->gen.freq >= 0,
1730  "frequency must be a non-negative number");
1731  } else if (strcmp(cmd, "OUT_FREQ") == 0 && n_comps == 2 &&
1732  info != NULL && info->type == ELEC_INV) {
1733  info->tru.out_freq = atof(comps[1]);
1734  CHECK_COMP(info->tru.out_freq > 0,
1735  "frequency must be a positive number");
1736  } else if (strcmp(cmd, "IN_VOLTS") == 0 && n_comps == 2 &&
1737  info != NULL && (info->type == ELEC_TRU ||
1738  info->type == ELEC_INV)) {
1739  info->tru.in_volts = atof(comps[1]);
1740  } else if (strcmp(cmd, "IN_VOLTS") == 0 && n_comps == 2 &&
1741  info != NULL && info->type == ELEC_XFRMR) {
1742  info->xfrmr.in_volts = atof(comps[1]);
1743  } else if (strcmp(cmd, "OUT_VOLTS") == 0 && n_comps == 2 &&
1744  info != NULL && (info->type == ELEC_TRU ||
1745  info->type == ELEC_INV)) {
1746  info->tru.out_volts = atof(comps[1]);
1747  } else if (strcmp(cmd, "OUT_VOLTS") == 0 && n_comps == 2 &&
1748  info != NULL && info->type == ELEC_XFRMR) {
1749  info->xfrmr.out_volts = atof(comps[1]);
1750  } else if (strcmp(cmd, "MIN_VOLTS") == 0 && n_comps == 2 &&
1751  info != NULL && info->type == ELEC_LOAD) {
1752  info->load.min_volts = atof(comps[1]);
1753  } else if (strcmp(cmd, "MIN_VOLTS") == 0 && n_comps == 2 &&
1754  info != NULL && (info->type == ELEC_TRU ||
1755  info->type == ELEC_INV)) {
1756  info->tru.min_volts = atof(comps[1]);
1757  CHECK_COMP(info->tru.min_volts < info->tru.out_volts,
1758  "minimum voltage must be lower than nominal "
1759  "output voltage");
1760  } else if (strcmp(cmd, "INCAP") == 0 &&
1761  (n_comps == 3 || n_comps == 4) &&
1762  info != NULL && info->type == ELEC_LOAD) {
1763  info->load.incap_C = atof(comps[1]);
1764  CHECK_COMP_V(info->load.incap_C > 0, "invalid input "
1765  "capacitance %s: must be positive (in Farads)",
1766  comps[1]);
1767  info->load.incap_R = atof(comps[2]);
1768  CHECK_COMP_V(info->load.incap_R > 0, "invalid input "
1769  "capacitance internal resistance %s: must be "
1770  "positive (in Ohms)", comps[2]);
1771  if (n_comps == 4) {
1772  info->load.incap_leak_Qps = atof(comps[3]);
1773  } else {
1774  info->load.incap_leak_Qps =
1775  info->load.incap_C / 200;
1776  }
1777  } else if (strcmp(cmd, "CAPACITY") == 0 && n_comps == 2 &&
1778  info != NULL && info->type == ELEC_BATT) {
1779  info->batt.capacity = atof(comps[1]);
1780  CHECK_COMP(info->batt.capacity > 0, "battery CAPACITY "
1781  "must be positive (in Watt-Hours)");
1782  } else if (strcmp(cmd, "STAB") == 0 && n_comps == 2 &&
1783  info != NULL && info->type == ELEC_LOAD) {
1784  info->load.stab = (strcmp(comps[1], "TRUE") == 0);
1785  } else if (strcmp(cmd, "STAB_RATE") == 0 && n_comps == 2 &&
1786  info != NULL && info->type == ELEC_GEN) {
1787  info->gen.stab_rate_U = atof(comps[1]);
1788  } else if (strcmp(cmd, "STAB_RATE_F") == 0 && n_comps == 2 &&
1789  info != NULL && info->type == ELEC_GEN) {
1790  CHECK_COMP(info->gen.freq != 0, "cannot define "
1791  "frequency stabilization rate for DC generators, "
1792  "or you must place the FREQ line before the "
1793  "STAB_RATE_F line");
1794  info->gen.stab_rate_f = atof(comps[1]);
1795  } else if (strcmp(cmd, "EXC_RPM") == 0 && n_comps == 2 &&
1796  info != NULL && info->type == ELEC_GEN) {
1797  info->gen.exc_rpm = atof(comps[1]);
1798  CHECK_COMP(info->gen.exc_rpm >= 0,
1799  "excitation rpm must be non-negative");
1800  } else if (strcmp(cmd, "MIN_RPM") == 0 && n_comps == 2 &&
1801  info != NULL && info->type == ELEC_GEN) {
1802  info->gen.min_rpm = atof(comps[1]);
1803  CHECK_COMP(info->gen.min_rpm > 0,
1804  "generator MIN_RPM must be positive");
1805  } else if (strcmp(cmd, "MAX_RPM") == 0 && n_comps == 2 &&
1806  info != NULL && info->type == ELEC_GEN) {
1807  info->gen.max_rpm = atof(comps[1]);
1808  CHECK_COMP(info->gen.max_rpm > 0,
1809  "generator MAX_RPM must be positive");
1810  } else if (strcmp(cmd, "RATE") == 0 && n_comps == 2 &&
1811  info != NULL && info->type == ELEC_CB) {
1812  info->cb.rate = clamp(atof(comps[1]), 0.001, 1000);
1813  } else if (strcmp(cmd, "MAX_PWR") == 0 && n_comps == 2 &&
1814  info != NULL && info->type == ELEC_BATT) {
1815  info->batt.max_pwr = atof(comps[1]);
1816  CHECK_COMP(info->batt.max_pwr > 0,
1817  "MAX_PWR must be positive (in Watts)");
1818  } else if (strcmp(cmd, "CURVEPT") == 0 && n_comps == 4 &&
1819  info != NULL) {
1820  vect2_t **curve_pp;
1821  size_t num = 0;
1822 
1823  switch (info->type) {
1824  case ELEC_GEN:
1825  curve_pp = &info->gen.eff_curve;
1826  break;
1827  case ELEC_TRU:
1828  case ELEC_INV:
1829  curve_pp = &info->tru.eff_curve;
1830  break;
1831  case ELEC_XFRMR:
1832  curve_pp = &info->xfrmr.eff_curve;
1833  break;
1834  default:
1835  INVALID_LINE_FOR_COMP_TYPE;
1836  break;
1837  }
1838  if (*curve_pp != NULL) {
1839  for (vect2_t *curve = *curve_pp;
1840  !IS_NULL_VECT(*curve); curve++)
1841  num++;
1842  } else {
1843  num = 0;
1844  }
1845  *curve_pp = safe_realloc(*curve_pp,
1846  (num + 2) * sizeof (vect2_t));
1847  (*curve_pp)[num] =
1848  VECT2(atof(comps[2]), atof(comps[3]));
1849  (*curve_pp)[num + 1] = NULL_VECT2;
1850  } else if (strcmp(cmd, "ENDPT") == 0 && info != NULL &&
1851  info->type == ELEC_BUS && (n_comps == 2 || n_comps == 3)) {
1852  uint64_t cur_ID = crc64(comps[1], strlen(comps[1]));
1853  elec_comp_info_t *info2 =
1854  find_comp_info(infos, num_comps, comps[1]);
1855 
1856  if (info2 == NULL) {
1857  logMsg("%s:%d: unknown component %s",
1858  filename, linenum, comps[1]);
1859  free_strlist(comps, n_comps);
1860  goto errout;
1861  }
1862  for (unsigned i = 0; i < bus_ID_cur; i++) {
1863  if (bus_IDs_seen[i] == cur_ID) {
1864  logMsg("%s:%d: duplicate endpoint %s",
1865  filename, linenum, comps[1]);
1866  free_strlist(comps, n_comps);
1867  goto errout;
1868  }
1869  }
1870  if (bus_ID_cur < MAX_BUS_UNIQ)
1871  bus_IDs_seen[bus_ID_cur++] = cur_ID;
1872  if (!add_info_link(info, info2,
1873  (n_comps == 3 ? comps[2] : NULL)) ||
1874  !add_info_link(info2, info,
1875  (n_comps == 3 ? comps[2] : NULL))) {
1876  logMsg("%s:%d: bad component link line",
1877  filename, linenum);
1878  free_strlist(comps, n_comps);
1879  goto errout;
1880  }
1881  } else if ((strcmp(cmd, "LOADCB") == 0 ||
1882  strcmp(cmd, "LOADCB3") == 0) && (n_comps == 2 ||
1883  n_comps == 3) && info != NULL && info->type == ELEC_LOAD) {
1884  elec_comp_info_t *cb, *bus;
1885 
1886  ASSERT3U(comp_i + 1, <, num_comps);
1887  cb = &infos[comp_i++];
1888  cb->parse_linenum = linenum;
1889  cb->type = ELEC_CB;
1890  cb->name = sprintf_alloc("CB_%s", info->name);
1891  cb->cb.rate = 1;
1892  cb->cb.max_amps = atof(comps[1]);
1893  cb->cb.triphase = (strcmp(cmd, "LOADCB3") == 0);
1894  cb->autogen = true;
1895  if (n_comps == 3) {
1896  strlcpy(cb->location, comps[2],
1897  sizeof (cb->location));
1898  }
1899 
1900  bus = &infos[comp_i++];
1901  bus->parse_linenum = linenum;
1902  bus->type = ELEC_BUS;
1903  bus->name = sprintf_alloc("CB_BUS_%s", info->name);
1904  bus->bus.ac = info->load.ac;
1905  bus->autogen = true;
1906 
1907  VERIFY(add_info_link(bus, info, NULL));
1908  VERIFY(add_info_link(bus, cb, NULL));
1909  ASSERT_MSG(!cb->cb.triphase || info->load.ac,
1910  "Can't connect 3-phase CB %s to a DC bus",
1911  info->name);
1912  } else if (strcmp(cmd, "STD_LOAD") == 0 && n_comps == 2 &&
1913  info != NULL && info->type == ELEC_LOAD) {
1914  info->load.std_load = atof(comps[1]);
1915  } else if (strcmp(cmd, "FUSE") == 0 && n_comps == 1 &&
1916  info != NULL && info->type == ELEC_CB) {
1917  info->cb.fuse = true;
1918  } else if (strcmp(cmd, "GUI_POS") == 0 && (n_comps == 3 ||
1919  n_comps == 4) && info != NULL) {
1920  info->gui.pos = VECT2(atof(comps[1]), atof(comps[2]));
1921  if (n_comps == 4)
1922  info->gui.sz = atof(comps[3]);
1923  else
1924  info->gui.sz = 1;
1925  } else if (strcmp(cmd, "GUI_ROT") == 0 && n_comps == 2 &&
1926  info != NULL) {
1927  info->gui.rot = atof(comps[1]);
1928  } else if (strcmp(cmd, "GUI_LOAD") == 0 && n_comps == 2 &&
1929  info != NULL) {
1930  info->gui.load_type = str2load_type(comps[1]);
1931  } else if (strcmp(cmd, "GUI_VIRT") == 0 && n_comps == 1 &&
1932  info != NULL) {
1933  info->gui.virt = true;
1934  } else if (strcmp(cmd, "GUI_INVIS") == 0 && n_comps == 1 &&
1935  info != NULL) {
1936  info->gui.invis = true;
1937  } else if (strcmp(cmd, "GUI_COLOR") == 0 && n_comps == 4 &&
1938  info != NULL) {
1939  info->gui.color = VECT3(atof(comps[1]),
1940  atof(comps[2]), atof(comps[3]));
1941  } else if (strcmp(cmd, "CHGR_BATT") == 0 && n_comps == 4 &&
1942  info != NULL && info->type == ELEC_TRU) {
1943  info->tru.charger = true;
1944  info->tru.batt =
1945  find_comp_info(infos, num_comps, comps[1]);
1946  if (info->tru.batt == NULL) {
1947  logMsg("%s:%d: unknown component %s",
1948  filename, linenum, comps[1]);
1949  free_strlist(comps, n_comps);
1950  goto errout;
1951  }
1952  info->tru.batt_conn =
1953  find_comp_info(infos, num_comps, comps[2]);
1954  if (info->tru.batt_conn == NULL) {
1955  logMsg("%s:%d: unknown component %s",
1956  filename, linenum, comps[1]);
1957  free_strlist(comps, n_comps);
1958  goto errout;
1959  }
1960  info->tru.curr_lim = atof(comps[3]);
1961  if (info->tru.curr_lim <= 0) {
1962  logMsg("%s:%d: current limit must be positive",
1963  filename, linenum);
1964  free_strlist(comps, n_comps);
1965  goto errout;
1966  }
1967  } else if (strcmp(cmd, "CHG_R") == 0 && n_comps == 2 &&
1968  info != NULL && info->type == ELEC_BATT) {
1969  info->batt.chg_R = atof(comps[1]);
1970  CHECK_COMP(info->batt.chg_R > 0,
1971  "charge resistance must be positive");
1972  } else if (strcmp(cmd, "INT_R") == 0 && n_comps == 2 &&
1973  info != NULL && (info->type == ELEC_BATT ||
1974  info->type == ELEC_GEN || info->type == ELEC_TRU ||
1975  info->type == ELEC_INV || info->type == ELEC_XFRMR)) {
1976  info->int_R = atof(comps[1]);
1977  CHECK_COMP(info->int_R > 0,
1978  "internal resistance must be positive");
1979  } else if (strcmp(cmd, "LOCATION") == 0 && n_comps == 2 &&
1980  info != NULL) {
1981  strlcpy(info->location, comps[1],
1982  sizeof (info->location));
1983  } else {
1984  logMsg("%s:%d: unknown or malformed line",
1985  filename, linenum);
1986  free_strlist(comps, n_comps);
1987  goto errout;
1988  }
1989  free_strlist(comps, n_comps);
1990  }
1991 
1992  if (!validate_elec_comp_infos_parse(infos, num_comps, filename))
1993  goto errout;
1994 
1995 #undef INVALID_LINE_FOR_COMP_TYPE
1996 #undef CHECK_DUP_NAME
1997 #undef CHECK_COMP
1998 #undef CHECK_COMP_V
1999 
2000  fclose(fp);
2001  free(line);
2002  *num_infos = num_comps;
2003 
2004  return (infos);
2005 errout:
2006  fclose(fp);
2007  free(line);
2008  *num_infos = 0;
2009 
2010  return (NULL);
2011 }
2012 
2013 static void
2014 infos_free(elec_comp_info_t *infos, size_t num_infos)
2015 {
2016  ASSERT(infos != NULL || num_infos == 0);
2017  for (size_t i = 0; i < num_infos; i++) {
2018  elec_comp_info_t *info = &infos[i];
2019 
2020  free(info->name);
2021  if (info->type == ELEC_GEN)
2022  free(info->gen.eff_curve);
2023  else if (info->type == ELEC_TRU || info->type == ELEC_INV)
2024  free(info->tru.eff_curve);
2025  else if (info->type == ELEC_XFRMR)
2026  free(info->xfrmr.eff_curve);
2027  else if (info->type == ELEC_BUS)
2028  ZERO_FREE_N(info->bus.comps, info->bus.n_comps);
2029  }
2030  ZERO_FREE_N(infos, num_infos);
2031 }
2032 
2047 void
2048 libelec_add_user_cb(elec_sys_t *sys, bool pre, elec_user_cb_t cb,
2049  void *userinfo)
2050 {
2051  user_cb_info_t *info = safe_calloc(1, sizeof (*info));
2052  avl_index_t where;
2053 
2054  ASSERT(sys != NULL);
2055  ASSERT(cb != NULL);
2056 
2057  info->pre = !!pre;
2058  info->cb = cb;
2059  info->userinfo = userinfo;
2060 
2061  mutex_enter(&sys->user_cbs_lock);
2062  VERIFY3P(avl_find(&sys->user_cbs, info, &where), ==, NULL);
2063  avl_insert(&sys->user_cbs, info, where);
2064  mutex_exit(&sys->user_cbs_lock);
2065 }
2066 
2075 void
2076 libelec_remove_user_cb(elec_sys_t *sys, bool pre, elec_user_cb_t cb,
2077  void *userinfo)
2078 {
2079  user_cb_info_t srch, *info;
2080 
2081  ASSERT(sys != NULL);
2082  ASSERT(cb != NULL);
2083 
2084  srch.pre = !!pre;
2085  srch.cb = cb;
2086  srch.userinfo = userinfo;
2087 
2088  mutex_enter(&sys->user_cbs_lock);
2089  info = avl_find(&sys->user_cbs, &srch, NULL);
2090  VERIFY(info != NULL);
2091  avl_remove(&sys->user_cbs, info);
2092  mutex_exit(&sys->user_cbs_lock);
2093 
2094  ZERO_FREE(info);
2095 }
2096 
2107 void
2108 libelec_walk_comps(const elec_sys_t *sys, void (*cb)(elec_comp_t *, void*),
2109  void *userinfo)
2110 {
2111  ASSERT(sys != NULL);
2112  ASSERT(cb != NULL);
2113  /* userinfo can be NULL */
2114 
2115  /*
2116  * The component list is immutable, so we can walk it without
2117  * holding the lock.
2118  */
2119  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
2120  comp = list_next(&sys->comps, comp)) {
2121  cb(comp, userinfo);
2122  }
2123 }
2124 
2130 const elec_comp_info_t *
2131 libelec_comp2info(const elec_comp_t *comp)
2132 {
2133  ASSERT(comp != NULL);
2134  /* immutable binding */
2135  return (comp->info);
2136 }
2137 
2146 elec_comp_t *
2147 libelec_comp_find(elec_sys_t *sys, const char *name)
2148 {
2149  elec_comp_info_t srch_info = { .name = (char *)name };
2150  const elec_comp_t srch_comp = { .info = &srch_info };
2151 
2152  ASSERT(sys != NULL);
2153  /* Component list is immutable, no need to lock */
2154  return (avl_find(&sys->name2comp, &srch_comp, NULL));
2155 }
2156 
2163 size_t
2164 libelec_comp_get_num_conns(const elec_comp_t *comp)
2165 {
2166  ASSERT(comp != NULL);
2167  ASSERT(comp->info != NULL);
2168 
2169  /* Electrical configuration is immutable, no need to lock */
2170  switch (comp->info->type) {
2171  case ELEC_BUS:
2172  return (comp->n_links);
2173  case ELEC_TIE:
2174  return (comp->n_links);
2175  case ELEC_TRU:
2176  case ELEC_INV:
2177  case ELEC_XFRMR:
2178  case ELEC_CB:
2179  case ELEC_SHUNT:
2180  case ELEC_DIODE:
2181  return (2);
2182  default:
2183  return (1);
2184  }
2185 }
2186 
2193 elec_comp_t *
2194 libelec_comp_get_conn(const elec_comp_t *comp, size_t i)
2195 {
2196  ASSERT(comp != NULL);
2197  ASSERT(comp->info != NULL);
2198  ASSERT3U(i, <, comp->n_links);
2199  /* Electrical configuration is immutable, no need to lock */
2200  return (comp->links[i].comp);
2201 }
2202 
2231 double
2232 libelec_comp_get_in_volts(const elec_comp_t *comp)
2233 {
2234  double volts;
2235 
2236  ASSERT(comp != NULL);
2237 
2238  NET_ADD_RECV_COMP(comp);
2239  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2240  volts = comp->ro.in_volts;
2241  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2242 
2243  return (volts);
2244 }
2245 
2275 double
2276 libelec_comp_get_out_volts(const elec_comp_t *comp)
2277 {
2278  double volts;
2279 
2280  ASSERT(comp != NULL);
2281 
2282  NET_ADD_RECV_COMP(comp);
2283  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2284  volts = comp->ro.out_volts;
2285  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2286 
2287  return (volts);
2288 }
2289 
2322 double
2323 libelec_comp_get_in_amps(const elec_comp_t *comp)
2324 {
2325  double amps;
2326 
2327  ASSERT(comp != NULL);
2328 
2329  NET_ADD_RECV_COMP(comp);
2330  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2331  amps = comp->ro.in_amps * (1 - comp->ro.leak_factor);
2332  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2333 
2334  return (amps);
2335 }
2336 
2359 double
2360 libelec_comp_get_out_amps(const elec_comp_t *comp)
2361 {
2362  double amps;
2363 
2364  ASSERT(comp != NULL);
2365 
2366  NET_ADD_RECV_COMP(comp);
2367  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2368  amps = comp->ro.out_amps * (1 - comp->ro.leak_factor);
2369  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2370 
2371  return (amps);
2372 }
2373 
2409 double
2410 libelec_comp_get_in_pwr(const elec_comp_t *comp)
2411 {
2412  double watts;
2413 
2414  ASSERT(comp != NULL);
2415 
2416  NET_ADD_RECV_COMP(comp);
2417  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2418  watts = comp->ro.in_pwr * (1 - comp->ro.leak_factor);
2419  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2420 
2421  return (watts);
2422 }
2423 
2468 double
2469 libelec_comp_get_out_pwr(const elec_comp_t *comp)
2470 {
2471  double watts;
2472 
2473  ASSERT(comp != NULL);
2474 
2475  NET_ADD_RECV_COMP(comp);
2476  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2477  watts = comp->ro.out_pwr * (1 - comp->ro.leak_factor);
2478  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2479 
2480  return (watts);
2481 }
2482 
2503 double
2504 libelec_comp_get_in_freq(const elec_comp_t *comp)
2505 {
2506  double freq;
2507 
2508  ASSERT(comp != NULL);
2509 
2510  NET_ADD_RECV_COMP(comp);
2511  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2512  freq = comp->ro.in_freq;
2513  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2514 
2515  return (freq);
2516 }
2517 
2545 double
2546 libelec_comp_get_out_freq(const elec_comp_t *comp)
2547 {
2548  double freq;
2549 
2550  ASSERT(comp != NULL);
2551 
2552  NET_ADD_RECV_COMP(comp);
2553  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2554  freq = comp->ro.out_freq;
2555  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2556 
2557  return (freq);
2558 }
2559 
2573 double
2574 libelec_comp_get_incap_volts(const elec_comp_t *comp)
2575 {
2576  ASSERT(comp != NULL);
2577  ASSERT(comp->info != NULL);
2578  ASSERT3U(comp->info->type, ==, ELEC_LOAD);
2579  return (comp->load.incap_U);
2580 }
2581 
2589 bool
2590 libelec_comp_is_AC(const elec_comp_t *comp)
2591 {
2592  ASSERT(comp != NULL);
2593  ASSERT(comp->info != NULL);
2594  switch (comp->info->type) {
2595  case ELEC_BATT:
2596  case ELEC_DIODE:
2597  return (false);
2598  case ELEC_TRU:
2599  case ELEC_INV:
2600  case ELEC_XFRMR:
2601  return (true);
2602  case ELEC_GEN:
2603  return (comp->info->gen.freq != 0);
2604  case ELEC_LOAD:
2605  ASSERT(comp->links[0].comp != NULL);
2606  return (comp->links[0].comp->info->bus.ac);
2607  case ELEC_BUS:
2608  return (comp->info->bus.ac);
2609  case ELEC_CB:
2610  case ELEC_SHUNT:
2611  ASSERT(comp->links[0].comp != 0);
2612  return (comp->links[0].comp->info->bus.ac);
2613  break;
2614  case ELEC_TIE:
2615  ASSERT(comp->n_links != 0);
2616  ASSERT(comp->links[0].comp != NULL);
2617  return (comp->links[0].comp->info->bus.ac);
2618  case ELEC_LABEL_BOX:
2619  VERIFY_FAIL();
2620  }
2621  VERIFY_FAIL();
2622 }
2623 
2625 libelec_comp_get_type(const elec_comp_t *comp)
2626 {
2627  ASSERT(comp != NULL);
2628  return (comp->info->type);
2629 }
2630 
2631 const char *
2632 libelec_comp_get_name(const elec_comp_t *comp)
2633 {
2634  ASSERT(comp != NULL);
2635  return (comp->info->name);
2636 }
2637 
2638 const char *
2639 libelec_comp_get_location(const elec_comp_t *comp)
2640 {
2641  ASSERT(comp != NULL);
2642  return (comp->info->location);
2643 }
2644 
2645 bool
2646 libelec_comp_get_autogen(const elec_comp_t *comp)
2647 {
2648  ASSERT(comp != NULL);
2649  return (comp->info->autogen);
2650 }
2651 
2669 bool
2670 libelec_comp_is_powered(const elec_comp_t *comp)
2671 {
2672  ASSERT(comp != NULL);
2673  return (libelec_comp_get_out_volts(comp) != 0);
2674 }
2675 
2681 double
2682 libelec_comp_get_eff(const elec_comp_t *comp)
2683 {
2684  double eff;
2685  ASSERT(comp != NULL);
2686  switch (comp->info->type) {
2687  case ELEC_GEN:
2688  mutex_enter(&((elec_comp_t *)comp)->gen.lock);
2689  eff = comp->gen.eff;
2690  mutex_exit(&((elec_comp_t *)comp)->gen.lock);
2691  break;
2692  case ELEC_TRU:
2693  case ELEC_INV:
2694  eff = comp->tru.eff;
2695  break;
2696  case ELEC_XFRMR:
2697  eff = comp->xfrmr.eff;
2698  break;
2699  default:
2700  VERIFY_FAIL();
2701  }
2702  return (eff);
2703 }
2704 
2705 unsigned
2706 libelec_comp_get_srcs(const elec_comp_t *comp,
2707  elec_comp_t *srcs[CONST_ARRAY_LEN_ARG(ELEC_MAX_SRCS)])
2708 {
2709  ASSERT(comp != NULL);
2710  ASSERT(srcs != NULL);
2711 
2712  mutex_enter((mutex_t *)&comp->rw_ro_lock);
2713  memcpy(srcs, comp->srcs_ext, sizeof (elec_comp_t *) * ELEC_MAX_SRCS);
2714  mutex_exit((mutex_t *)&comp->rw_ro_lock);
2715 
2716  for (unsigned i = 0; i < ELEC_MAX_SRCS; i++) {
2717  if (srcs[i] == NULL)
2718  return (i);
2719  }
2720  return (ELEC_MAX_SRCS);
2721 }
2722 
2741 void
2742 libelec_comp_set_failed(elec_comp_t *comp, bool failed)
2743 {
2744  ASSERT(comp != NULL);
2745  mutex_enter(&comp->rw_ro_lock);
2746  comp->ro.failed = failed;
2747  mutex_exit(&comp->rw_ro_lock);
2748 }
2749 
2754 bool
2755 libelec_comp_get_failed(const elec_comp_t *comp)
2756 {
2757  ASSERT(comp != NULL);
2758  NET_ADD_RECV_COMP(comp);
2759  return (comp->ro.failed);
2760 }
2761 
2774 void
2775 libelec_comp_set_shorted(elec_comp_t *comp, bool shorted)
2776 {
2777  ASSERT(comp != NULL);
2778  mutex_enter(&comp->rw_ro_lock);
2779  comp->ro.shorted = shorted;
2780  mutex_exit(&comp->rw_ro_lock);
2781 }
2782 
2788 bool
2789 libelec_comp_get_shorted(const elec_comp_t *comp)
2790 {
2791  ASSERT(comp != NULL);
2792  NET_ADD_RECV_COMP(comp);
2793  return (comp->ro.shorted);
2794 }
2795 
2796 static double
2797 gen_set_random_param(elec_comp_t *comp, double *param, double norm_value,
2798  double stddev)
2799 {
2800  double new_param;
2801 
2802  ASSERT(comp != NULL);
2803  ASSERT(param != NULL);
2804  ASSERT3F(stddev, >=, 0);
2805 
2806  if (stddev != 0) {
2807  new_param = norm_value + crc64_rand_normal(stddev);
2808  /*
2809  * Make sure the error is at least 0.5 standard deviations
2810  * and at most 1.5 standard deviations. This is to guarantee
2811  * that at least something is observed by the user.
2812  */
2813  if (new_param > norm_value) {
2814  new_param = clamp(new_param, norm_value + 0.5 * stddev,
2815  norm_value + 1.5 * stddev);
2816  } else {
2817  new_param = clamp(new_param, norm_value - 1.5 * stddev,
2818  norm_value - 0.5 * stddev);
2819  }
2820  } else {
2821  new_param = norm_value;
2822  }
2823  mutex_enter(&comp->sys->worker_interlock);
2824  *param = norm_value;
2825  mutex_exit(&comp->sys->worker_interlock);
2826 
2827  return (new_param);
2828 }
2829 
2848 double
2849 libelec_gen_set_random_volts(elec_comp_t *comp, double stddev)
2850 {
2851  ASSERT(comp != NULL);
2852  ASSERT3U(comp->info->type, ==, ELEC_GEN);
2853  ASSERT3F(1.5 * stddev, <, comp->info->gen.volts);
2854  return (gen_set_random_param(comp, &comp->gen.tgt_volts,
2855  comp->info->gen.volts, stddev));
2856 }
2857 
2863 double
2864 libelec_gen_set_random_freq(elec_comp_t *comp, double stddev)
2865 {
2866  ASSERT(comp != NULL);
2867  ASSERT3U(comp->info->type, ==, ELEC_GEN);
2868  ASSERT(libelec_comp_is_AC(comp));
2869  ASSERT3F(comp->info->gen.freq - 1.5 * stddev, >, 0);
2870  return (gen_set_random_param(comp, &comp->gen.tgt_freq,
2871  comp->info->gen.freq, stddev));
2872 }
2873 
2881 void
2882 libelec_comp_set_userinfo(elec_comp_t *comp, void *userinfo)
2883 {
2884  ASSERT(comp != NULL);
2885  ASSERT(!comp->sys->started);
2886  comp->info->userinfo = userinfo;
2887 }
2888 
2894 void *
2895 libelec_comp_get_userinfo(const elec_comp_t *comp)
2896 {
2897  ASSERT(comp != NULL);
2898  return (comp->info->userinfo);
2899 }
2900 
2917 void
2919 {
2920  ASSERT(batt != NULL);
2921  ASSERT3U(batt->info->type, ==, ELEC_BATT);
2922  ASSERT(!batt->sys->started);
2923  batt->info->batt.get_temp = cb;
2924 }
2925 
2933 libelec_batt_get_temp_cb(const elec_comp_t *batt)
2934 {
2935  ASSERT(batt != NULL);
2936  ASSERT3U(batt->info->type, ==, ELEC_BATT);
2937  return (batt->info->batt.get_temp);
2938 }
2939 
2955 void
2957 {
2958  ASSERT(gen != NULL);
2959  ASSERT3U(gen->info->type, ==, ELEC_GEN);
2960  ASSERT(!gen->sys->started);
2961  gen->info->gen.get_rpm = cb;
2962 }
2963 
2970 libelec_gen_get_rpm_cb(const elec_comp_t *gen)
2971 {
2972  ASSERT(gen != NULL);
2973  ASSERT3U(gen->info->type, ==, ELEC_GEN);
2974  return (gen->info->gen.get_rpm);
2975 }
2976 
2992 void
2994 {
2995  ASSERT(load != NULL);
2996  ASSERT3U(load->info->type, ==, ELEC_LOAD);
2997  ASSERT(!load->sys->started);
2998  load->info->load.get_load = cb;
2999 }
3000 
3007 libelec_load_get_load_cb(elec_comp_t *load)
3008 {
3009  ASSERT(load != NULL);
3010  ASSERT3U(load->info->type, ==, ELEC_LOAD);
3011  return (load->info->load.get_load);
3012 }
3013 
3014 static void
3015 update_short_leak_factor(elec_comp_t *comp, double d_t)
3016 {
3017  ASSERT(comp != NULL);
3018  ASSERT3F(d_t, >, 0);
3019 
3020  if (comp->rw.shorted) {
3021  /*
3022  * Gradually ramp up the leak to give the breaker a bit of
3023  * time to stay pushed in.
3024  */
3025  if (comp->info->type == ELEC_LOAD) {
3026  if (comp->ro.in_pwr != 0)
3027  FILTER_IN(comp->rw.leak_factor, 0.99, d_t, 1);
3028  else
3029  comp->rw.leak_factor = 0;
3030  } else {
3031  comp->rw.leak_factor =
3032  wavg(0.97, 0.975, crc64_rand_fract());
3033  }
3034  } else {
3035  comp->rw.leak_factor = 0;
3036  }
3037 }
3038 
3039 static void
3040 network_reset(elec_sys_t *sys, double d_t)
3041 {
3042  ASSERT(sys != NULL);
3043  ASSERT_MUTEX_HELD(&sys->worker_interlock);
3044  ASSERT3F(d_t, >, 0);
3045 
3046  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
3047  comp = list_next(&sys->comps, comp)) {
3048  elec_comp_t *srcs_ext[ELEC_MAX_SRCS];
3049 
3050  memcpy(srcs_ext, comp->srcs, sizeof (srcs_ext));
3051 
3052  comp->rw.in_volts = 0;
3053  comp->rw.in_pwr = 0;
3054  comp->rw.in_amps = 0;
3055  comp->rw.in_freq = 0;
3056  comp->rw.out_volts = 0;
3057  comp->rw.out_pwr = 0;
3058  comp->rw.out_amps = 0;
3059  comp->rw.out_freq = 0;
3060  comp->rw.short_amps = 0;
3061  comp->src_int_cond_total = 0;
3062  memset(comp->srcs, 0, sizeof (comp->srcs));
3063  comp->n_srcs = 0;
3064  for (unsigned i = 0; i < comp->n_links; i++) {
3065  memset(comp->links[i].out_amps, 0,
3066  sizeof (comp->links[i].out_amps));
3067  }
3068 
3069  mutex_enter(&comp->rw_ro_lock);
3070  comp->rw.failed = comp->ro.failed;
3071  comp->rw.shorted = comp->ro.shorted;
3072  update_short_leak_factor(comp, d_t);
3073  memcpy(comp->srcs_ext, srcs_ext, sizeof (comp->srcs_ext));
3074  mutex_exit(&comp->rw_ro_lock);
3075 
3076  comp->integ_mask = 0;
3077  switch (comp->info->type) {
3078  case ELEC_LOAD:
3079  comp->load.seen = false;
3080  break;
3081  case ELEC_TIE:
3082  /* Transfer the latest tie state to the worker set */
3083  mutex_enter(&comp->tie.lock);
3084  memcpy(comp->tie.wk_state, comp->tie.cur_state,
3085  comp->n_links * sizeof (*comp->tie.wk_state));
3086  mutex_exit(&comp->tie.lock);
3087  break;
3088  case ELEC_CB:
3089 #ifdef LIBELEC_WITH_LIBSWITCH
3090  if (comp->scb.sw != NULL) {
3091  comp->scb.cur_set =
3092  !libswitch_read(comp->scb.sw, NULL);
3093  }
3094 #endif /* defined(LIBELEC_WITH_LIBSWITCH) */
3095  comp->scb.wk_set = comp->scb.cur_set;
3096  break;
3097  default:
3098  break;
3099  }
3100  }
3101 }
3102 
3103 static void
3104 network_update_gen(elec_comp_t *gen, double d_t)
3105 {
3106  ASSERT(gen != NULL);
3107  ASSERT(gen->info != NULL);
3108  ASSERT3U(gen->info->type, ==, ELEC_GEN);
3109 
3110  if (gen->info->gen.get_rpm != NULL) {
3111  double rpm = gen->info->gen.get_rpm(gen, gen->info->userinfo);
3112  ASSERT(!isnan(rpm));
3113  mutex_enter(&gen->gen.lock);
3114  gen->gen.rpm = MAX(rpm, GEN_MIN_RPM);
3115  mutex_exit(&gen->gen.lock);
3116  }
3117  if (gen->gen.rpm <= GEN_MIN_RPM) {
3118  gen->gen.stab_factor_U = 1;
3119  gen->gen.stab_factor_f = 1;
3120  gen->rw.in_volts = 0;
3121  gen->rw.in_freq = 0;
3122  gen->rw.out_volts = 0;
3123  gen->rw.out_freq = 0;
3124  return;
3125  }
3126  /*
3127  * Gradual voltage & frequency stabilization adjustment, to simulate
3128  * that the CSD takes a little time to adjust to rpm changes.
3129  */
3130  if (gen->info->gen.stab_rate_U > 0) {
3131  double stab_factor_U = clamp(gen->gen.ctr_rpm / gen->gen.rpm,
3132  gen->gen.min_stab_U, gen->gen.max_stab_U);
3133  double stab_rate_mod =
3134  clamp(1 + crc64_rand_normal(0.1), 0.1, 10);
3135  FILTER_IN(gen->gen.stab_factor_U, stab_factor_U, d_t,
3136  gen->info->gen.stab_rate_U * stab_rate_mod);
3137  } else {
3138  gen->gen.stab_factor_U = 1;
3139  }
3140  if (gen->info->gen.stab_rate_f > 0) {
3141  double stab_factor_f = clamp(gen->gen.ctr_rpm / gen->gen.rpm,
3142  gen->gen.min_stab_f, gen->gen.max_stab_f);
3143  double stab_rate_mod =
3144  clamp(1 + crc64_rand_normal(0.1), 0.1, 10);
3145  FILTER_IN(gen->gen.stab_factor_f, stab_factor_f, d_t,
3146  gen->info->gen.stab_rate_f * stab_rate_mod);
3147  } else {
3148  gen->gen.stab_factor_f = 1;
3149  }
3150  if (!gen->rw.failed) {
3151  if (gen->gen.rpm < gen->info->gen.exc_rpm) {
3152  gen->rw.in_volts = 0;
3153  gen->rw.in_freq = 0;
3154  } else {
3155  ASSERT(gen->gen.tgt_volts != 0);
3156  gen->rw.in_volts = (gen->gen.rpm / gen->gen.ctr_rpm) *
3157  gen->gen.stab_factor_U * gen->gen.tgt_volts;
3158  if (gen->gen.tgt_freq != 0) {
3159  gen->rw.in_freq = (gen->gen.rpm /
3160  gen->gen.ctr_rpm) *
3161  gen->gen.stab_factor_f * gen->gen.tgt_freq;
3162  }
3163  }
3164  gen->rw.out_volts = gen->rw.in_volts;
3165  gen->rw.out_freq = gen->rw.in_freq;
3166  } else {
3167  gen->rw.in_volts = 0;
3168  gen->rw.in_freq = 0;
3169  gen->rw.out_volts = 0;
3170  gen->rw.out_freq = 0;
3171  }
3172 }
3173 
3174 static void
3175 network_update_batt(elec_comp_t *batt, double d_t)
3176 {
3177  double U, J, temp_coeff, J_max, I_max, I_rel;
3178 
3179  ASSERT(batt != NULL);
3180  ASSERT(batt->info != NULL);
3181  ASSERT3U(batt->info->type, ==, ELEC_BATT);
3182 
3183  if (batt->info->batt.get_temp != NULL) {
3184  double T = batt->info->batt.get_temp(batt,
3185  batt->info->userinfo);
3186  ASSERT3F(T, >, 0);
3187  mutex_enter(&batt->batt.lock);
3188  batt->batt.T = T;
3189  mutex_exit(&batt->batt.lock);
3190  }
3191  temp_coeff = fx_lin_multi2(batt->batt.T, batt_temp_energy_curve,
3192  ARRAY_NUM_ELEM(batt_temp_energy_curve), true);
3193 
3194  I_max = batt->info->batt.max_pwr / batt->info->batt.volts;
3195  I_rel = batt->batt.prev_amps / I_max;
3196  U = libelec_phys_get_batt_voltage(batt->info->batt.volts,
3197  batt->batt.chg_rel, I_rel);
3198 
3199  J_max = batt->info->batt.capacity * temp_coeff;
3200  J = batt->batt.chg_rel * J_max;
3201  J -= U * batt->batt.prev_amps * d_t;
3202  /* Incorporate charging change */
3203  J += batt->batt.rechg_W * d_t;
3204  batt->batt.rechg_W = 0;
3205 
3206  /* Recalculate the new voltage and relative charge state */
3207  if (!batt->rw.failed) {
3208  batt->rw.in_volts = U;
3209  batt->rw.out_volts = U;
3210  } else {
3211  batt->rw.in_volts = 0;
3212  batt->rw.out_volts = 0;
3213  }
3214  /*
3215  * If the temperature is very cold, we might slightly overshoot
3216  * capacity here, so clamp to 0-1.
3217  */
3218  batt->batt.chg_rel = clamp(J / J_max, 0, 1);
3219 }
3220 
3221 static void
3222 network_update_cb(elec_comp_t *cb, double d_t)
3223 {
3224  double amps_rat;
3225 
3226  ASSERT(cb != NULL);
3227  ASSERT(cb->info != NULL);
3228  ASSERT3U(cb->info->type, ==, ELEC_CB);
3229  ASSERT3F(cb->info->cb.max_amps, >, 0);
3230 
3231  amps_rat = cb->rw.out_amps / cb->info->cb.max_amps;
3232  /* 3-phase CBs evenly split the power between themselves */
3233  if (cb->info->cb.triphase)
3234  amps_rat /= 3;
3235  amps_rat = MIN(amps_rat, 5 * cb->info->cb.rate);
3236  FILTER_IN(cb->scb.temp, amps_rat, d_t, cb->info->cb.rate);
3237 
3238  if (cb->scb.temp >= 1.0) {
3239  cb->scb.wk_set = false;
3240  cb->scb.cur_set = false;
3241 #ifdef LIBELEC_WITH_LIBSWITCH
3242  /* Also pull the switch if one is present */
3243  if (cb->scb.sw != NULL)
3244  libswitch_set(cb->scb.sw, true);
3245 #endif /* defined(LIBELEC_WITH_LIBSWITCH) */
3246  if (cb->info->cb.fuse)
3247  cb->rw.failed = true;
3248  }
3249 }
3250 
3251 static void
3252 network_update_tru(elec_comp_t *tru, double d_t)
3253 {
3254  ASSERT(tru != NULL);
3255  ASSERT(tru->info != NULL);
3256  ASSERT3U(tru->info->type, ==, ELEC_TRU);
3257  ASSERT3F(d_t, >, 0);
3258 
3259  if (tru->ro.in_volts < tru->info->tru.min_volts) {
3260  tru->tru.regul = 0;
3261  return;
3262  }
3263  if (!tru->info->tru.charger) {
3264  /*
3265  * In simple TRU operating mode, we operate as a fixed-rate
3266  * transformer. Our input voltage is directly proportional
3267  * to our output and we provide no output voltage regulation.
3268  */
3269  tru->tru.regul = 1;
3270  } else {
3271  /*
3272  * In charger mode, we control an additional voltage
3273  * regulator parameter that lets us adjust our output
3274  * voltage as necessary to stabilize the charging current.
3275  */
3276  ASSERT3F(tru->info->tru.curr_lim, >, 0);
3277  double oc_ratio = tru->tru.prev_amps / tru->info->tru.curr_lim;
3278  double regul_tgt = clamp(oc_ratio > 0 ?
3279  tru->tru.regul / oc_ratio : 1, 0, 1);
3280  /*
3281  * Don't enable the output if the battery isn't being sensed.
3282  */
3283  if (!libelec_tie_get_all(tru->tru.batt_conn) || oc_ratio > 4) {
3284  /*
3285  * When a super-high current consumer is persistently
3286  * on, we might blow our output breaker by trying to
3287  * keep on charging. So just back off completely and
3288  * slowly come up later to retry.
3289  */
3290  tru->tru.regul = 0;
3291  } else if (regul_tgt > tru->tru.regul) {
3292  FILTER_IN(tru->tru.regul, regul_tgt, d_t, 1);
3293  } else {
3294  FILTER_IN(tru->tru.regul, regul_tgt, d_t, 2 * d_t);
3295  }
3296  }
3297 }
3298 
3299 static void
3300 network_srcs_update(elec_sys_t *sys, double d_t)
3301 {
3302  ASSERT(sys != NULL);
3303  ASSERT_MUTEX_HELD(&sys->worker_interlock);
3304  ASSERT3F(d_t, >, 0);
3305 
3306  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
3307  comp = list_next(&sys->comps, comp)) {
3308  ASSERT(comp->info != NULL);
3309  switch (comp->info->type) {
3310  case ELEC_BATT:
3311  network_update_batt(comp, d_t);
3312  break;
3313  case ELEC_GEN:
3314  network_update_gen(comp, d_t);
3315  break;
3316  case ELEC_TRU:
3317  network_update_tru(comp, d_t);
3318  break;
3319  default:
3320  break;
3321  }
3322  }
3323 }
3324 
3325 static void
3326 network_loads_randomize(elec_sys_t *sys, double d_t)
3327 {
3328  ASSERT(sys != NULL);
3329  ASSERT3F(d_t, >, 0);
3330 
3331  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
3332  comp = list_next(&sys->comps, comp)) {
3333  if (comp->info->type == ELEC_LOAD) {
3334  FILTER_IN(comp->load.random_load_factor,
3335  clamp(1.0 + crc64_rand_normal(0.1), 0.8, 1.2),
3336  d_t, 0.25);
3337  }
3338  }
3339 }
3340 
3341 static void
3342 load_incap_update(elec_comp_t *comp, double d_t)
3343 {
3344  const elec_comp_info_t *info;
3345  double d_Q;
3346 
3347  ASSERT(comp != NULL);
3348  ASSERT(comp->info != NULL);
3349  ASSERT3U(comp->info->type, ==, ELEC_LOAD);
3350  ASSERT3F(d_t, >, 0);
3351 
3352  info = comp->info;
3353  if (info->load.incap_C == 0)
3354  return;
3355 
3356  d_Q = comp->load.incap_d_Q - info->load.incap_leak_Qps * d_t;
3357  comp->load.incap_U += d_Q / info->load.incap_C;
3358  comp->load.incap_U = MAX(comp->load.incap_U, 0);
3359  if (comp->rw.failed)
3360  comp->load.incap_U = 0;
3361 }
3362 
3363 static void
3364 network_loads_update(elec_sys_t *sys, double d_t)
3365 {
3366  ASSERT(sys != NULL);
3367  ASSERT3F(d_t, >, 0);
3368 
3369  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
3370  comp = list_next(&sys->comps, comp)) {
3371  ASSERT(comp->info != NULL);
3372 
3373  if (comp->info->type == ELEC_CB) {
3374  network_update_cb(comp, d_t);
3375  } else if (comp->info->type == ELEC_LOAD) {
3376  /*
3377  * If we haven't seen this component, that means we
3378  * need to run the load integration manually to take
3379  * care of input capacitance.
3380  */
3381  if (!comp->load.seen)
3382  network_load_integrate_load(NULL, comp, 0, d_t);
3383  load_incap_update(comp, d_t);
3384  }
3385 
3386  comp->rw.in_pwr = comp->rw.in_volts * comp->rw.in_amps;
3387  comp->rw.out_pwr = comp->rw.out_volts * comp->rw.out_amps;
3388  }
3389 }
3390 
3391 static void
3392 network_ties_update(elec_sys_t *sys)
3393 {
3394  ASSERT(sys != NULL);
3395 
3396  for (elec_comp_t *comp = list_head(&sys->ties); comp != NULL;
3397  comp = list_next(&sys->ties, comp)) {
3398  unsigned n_tied = 0;
3399  int tied[2] = { -1, -1 };
3400  for (unsigned i = 0; i < comp->n_links; i++) {
3401  if (comp->tie.wk_state[i]) {
3402  tied[n_tied] = i;
3403  n_tied++;
3404  if (n_tied == ARRAY_NUM_ELEM(tied))
3405  break;
3406  }
3407  }
3408  if (n_tied == 2) {
3409  double amps[2] = {
3410  sum_link_amps(&comp->links[tied[0]]),
3411  sum_link_amps(&comp->links[tied[1]])
3412  };
3413  comp->rw.out_amps = amps[0] - amps[1];
3414  comp->rw.out_amps = NO_NEG_ZERO(ABS(comp->rw.out_amps));
3415  comp->rw.in_amps = comp->rw.out_amps;
3416  }
3417  }
3418 }
3419 
3420 static inline void
3421 add_src_up(elec_comp_t *comp, elec_comp_t *src, const elec_comp_t *upstream)
3422 {
3423  ASSERT(comp != NULL);
3424  ASSERT(src != NULL);
3425  ASSERT(upstream != NULL);
3426 
3427  ASSERT0(src->src_mask & (1 << src->src_idx));
3428  ASSERT3U(comp->n_srcs, <, ELEC_MAX_SRCS);
3429  comp->srcs[comp->n_srcs] = src;
3430  comp->n_srcs++;
3431  ASSERT3F(src->info->int_R, >, 0);
3432  comp->src_int_cond_total += (1.0 / src->info->int_R) *
3433  src->rw.out_volts;
3434  for (unsigned i = 0; i < comp->n_links; i++) {
3435  if (comp->links[i].comp == upstream) {
3436  comp->links[i].srcs[src->src_idx] = src;
3437  return;
3438  }
3439  }
3440  VERIFY_FAIL();
3441 }
3442 
3443 static void
3444 network_paint_src_bus(elec_comp_t *src, elec_comp_t *upstream,
3445  elec_comp_t *comp, unsigned depth)
3446 {
3447  ASSERT(src != NULL);
3448  ASSERT(upstream != NULL);
3449  ASSERT(comp != NULL);
3450  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3451 
3452  if (comp->rw.failed)
3453  return;
3454 
3455  add_src_up(comp, src, upstream);
3456  if (comp->rw.in_volts < src->rw.out_volts) {
3457  comp->rw.in_volts = src->rw.out_volts;
3458  comp->rw.in_freq = src->rw.out_freq;
3459  comp->rw.out_volts = comp->rw.in_volts;
3460  comp->rw.out_freq = comp->rw.in_freq;
3461  }
3462  for (unsigned i = 0; i < comp->n_links; i++) {
3463  if (comp->links[i].comp != upstream) {
3464  network_paint_src_comp(src, comp,
3465  comp->links[i].comp, depth + 1);
3466  }
3467  }
3468 }
3469 
3470 static void
3471 network_paint_src_tie(elec_comp_t *src, elec_comp_t *upstream,
3472  elec_comp_t *comp, unsigned depth)
3473 {
3474  bool found = false, tied = false;
3475 
3476  ASSERT(src != NULL);
3477  ASSERT(upstream != NULL);
3478  ASSERT(comp != NULL);
3479  ASSERT(comp->info != NULL);
3480  ASSERT3U(comp->info->type, ==, ELEC_TIE);
3481  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3482 
3483  /* Check if the upstream bus is currently tied */
3484  for (unsigned i = 0; i < comp->n_links; i++) {
3485  if (upstream == comp->links[i].comp) {
3486  if (comp->tie.wk_state[i]) {
3487  add_src_up(comp, src, upstream);
3488  if (comp->rw.in_volts < src->rw.out_volts) {
3489  comp->rw.in_volts = src->rw.out_volts;
3490  comp->rw.in_freq = src->rw.out_freq;
3491  }
3492  tied = true;
3493  }
3494  found = true;
3495  break;
3496  }
3497  }
3498  ASSERT(found);
3499  if (tied) {
3500  for (unsigned i = 0; i < comp->n_links; i++) {
3501  if (upstream != comp->links[i].comp &&
3502  comp->tie.wk_state[i]) {
3503  network_paint_src_comp(src, comp,
3504  comp->links[i].comp, depth + 1);
3505  }
3506  }
3507  }
3508 }
3509 
3510 static void
3511 recalc_out_volts_tru(elec_comp_t *comp)
3512 {
3513  ASSERT(comp != NULL);
3514  ASSERT3U(comp->info->type, ==, ELEC_TRU);
3515  comp->rw.out_volts = comp->tru.regul * comp->info->tru.out_volts *
3516  (comp->rw.in_volts / comp->info->tru.in_volts);
3517 }
3518 
3519 static void
3520 recalc_out_volts_freq_inv(elec_comp_t *comp)
3521 {
3522  double mult_U, mult_f;
3523 
3524  ASSERT(comp != NULL);
3525  ASSERT3U(comp->info->type, ==, ELEC_INV);
3526 
3527  mult_U = fx_lin(comp->rw.in_volts, comp->info->tru.min_volts,
3528  0.95, comp->info->tru.in_volts, 1);
3529  mult_f = fx_lin(comp->rw.in_volts, comp->info->tru.min_volts,
3530  0.97, comp->info->tru.in_volts, 1);
3531  comp->rw.out_volts = mult_U * comp->info->tru.out_volts;
3532  comp->rw.out_freq = mult_f * comp->info->tru.out_freq;
3533 }
3534 
3535 static void
3536 network_paint_src_tru_inv(elec_comp_t *src, elec_comp_t *upstream,
3537  elec_comp_t *comp, unsigned depth)
3538 {
3539  ASSERT(src != NULL);
3540  ASSERT(upstream != NULL);
3541  ASSERT(comp != NULL);
3542  ASSERT(comp->info != NULL);
3543  ASSERT(comp->info->type == ELEC_TRU ||
3544  comp->info->type == ELEC_INV);
3545  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3546 
3547  /* Conversion prevents back-flow of power from output to input */
3548  if (upstream != comp->links[0].comp)
3549  return;
3550 
3551  add_src_up(comp, src, upstream);
3552  if (comp->info->type == ELEC_TRU) {
3553  ASSERT_MSG(comp->n_srcs == 1, "%s attempted to add a second "
3554  "AC power source ([0]=%s, [1]=%s). Multi-source feeding "
3555  "is NOT supported in AC networks.", comp->info->name,
3556  comp->srcs[0]->info->name, comp->srcs[1]->info->name);
3557  }
3558  if (!comp->rw.failed) {
3559  if (comp->rw.in_volts < src->rw.out_volts &&
3560  src->rw.out_volts > comp->info->tru.min_volts) {
3561  comp->rw.in_volts = src->rw.out_volts;
3562  comp->rw.in_freq = src->rw.out_freq;
3563  if (comp->info->type == ELEC_TRU)
3564  recalc_out_volts_tru(comp);
3565  else
3566  recalc_out_volts_freq_inv(comp);
3567  }
3568  } else {
3569  comp->rw.in_volts = 0;
3570  comp->rw.in_freq = 0;
3571  comp->rw.out_volts = 0;
3572  comp->rw.out_freq = 0;
3573  }
3574  ASSERT(comp->links[1].comp != NULL);
3575  /*
3576  * The TRU/inverter becomes the source for downstream buses.
3577  */
3578  if (comp->rw.out_volts != 0) {
3579  network_paint_src_comp(comp, comp,
3580  comp->links[1].comp, depth + 1);
3581  }
3582 }
3583 
3584 static void
3585 network_paint_src_xfrmr(elec_comp_t *src, elec_comp_t *upstream,
3586  elec_comp_t *comp, unsigned depth)
3587 {
3588  ASSERT(src != NULL);
3589  ASSERT(upstream != NULL);
3590  ASSERT(comp != NULL);
3591  ASSERT(comp->info != NULL);
3592  ASSERT(comp->info->type == ELEC_XFRMR);
3593  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3594 
3595  /* Transformers prevents back-flow of power from output to input */
3596  if (upstream != comp->links[0].comp)
3597  return;
3598 
3599  add_src_up(comp, src, upstream);
3600  ASSERT_MSG(comp->n_srcs == 1, "%s attempted to add a second "
3601  "AC power source ([0]=%s, [1]=%s). Multi-source feeding "
3602  "is NOT supported in AC networks.", comp->info->name,
3603  comp->srcs[0]->info->name, comp->srcs[1]->info->name);
3604 
3605  if (!comp->rw.failed) {
3606  if (comp->rw.in_volts < src->rw.out_volts) {
3607  comp->rw.in_volts = src->rw.out_volts;
3608  comp->rw.out_volts = comp->rw.in_volts *
3609  (comp->info->xfrmr.out_volts /
3610  comp->info->xfrmr.in_volts);
3611  comp->rw.in_freq = src->rw.out_freq;
3612  comp->rw.out_freq = comp->rw.in_freq;
3613  }
3614  } else {
3615  comp->rw.in_volts = 0;
3616  comp->rw.out_volts = 0;
3617  comp->rw.in_freq = 0;
3618  comp->rw.out_freq = 0;
3619  }
3620  ASSERT(comp->links[1].comp != NULL);
3621  /*
3622  * The transformer becomes the source for downstream buses.
3623  */
3624  if (comp->rw.out_volts != 0) {
3625  network_paint_src_comp(comp, comp,
3626  comp->links[1].comp, depth + 1);
3627  }
3628 }
3629 
3630 static void
3631 network_paint_src_scb(elec_comp_t *src, elec_comp_t *upstream,
3632  elec_comp_t *comp, unsigned depth)
3633 {
3634  ASSERT(src != NULL);
3635  ASSERT(upstream != NULL);
3636  ASSERT(comp != NULL);
3637  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3638 
3639  if (!comp->rw.failed && comp->scb.wk_set) {
3640  add_src_up(comp, src, upstream);
3641  if (comp->rw.in_volts < src->rw.out_volts) {
3642  comp->rw.in_volts = src->rw.out_volts;
3643  comp->rw.in_freq = src->rw.out_freq;
3644  comp->rw.out_volts = src->rw.out_volts;
3645  comp->rw.out_freq = src->rw.out_freq;
3646  }
3647  if (upstream == comp->links[0].comp) {
3648  ASSERT(comp->links[1].comp != NULL);
3649  network_paint_src_comp(src, comp,
3650  comp->links[1].comp, depth + 1);
3651  } else {
3652  ASSERT3P(upstream, ==, comp->links[1].comp);
3653  ASSERT(comp->links[0].comp != NULL);
3654  network_paint_src_comp(src, comp,
3655  comp->links[0].comp, depth + 1);
3656  }
3657  }
3658 }
3659 
3660 static void
3661 network_paint_src_diode(elec_comp_t *src, elec_comp_t *upstream,
3662  elec_comp_t *comp, unsigned depth)
3663 {
3664  ASSERT(src != NULL);
3665  ASSERT(upstream != NULL);
3666  ASSERT(comp != NULL);
3667  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3668 
3669  if (upstream == comp->links[0].comp) {
3670  add_src_up(comp, src, upstream);
3671  ASSERT0(src->rw.out_freq);
3672  if (!comp->rw.failed) {
3673  if (comp->rw.in_volts < src->rw.out_volts)
3674  comp->rw.in_volts = src->rw.out_volts;
3675  } else {
3676  comp->rw.in_volts = 0;
3677  }
3678  network_paint_src_comp(src, comp, comp->links[1].comp,
3679  depth + 1);
3680  }
3681 }
3682 
3683 static void
3684 network_paint_src_comp(elec_comp_t *src, elec_comp_t *upstream,
3685  elec_comp_t *comp, unsigned depth)
3686 {
3687  ASSERT(src != NULL);
3688  ASSERT(src->info != NULL);
3689  ASSERT(upstream != NULL);
3690  ASSERT(comp != NULL);
3691  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3692 
3693  switch (comp->info->type) {
3694  case ELEC_BATT:
3695  if (src != comp && comp->rw.out_volts < src->rw.out_volts)
3696  add_src_up(comp, src, upstream);
3697  break;
3698  case ELEC_GEN:
3699  break;
3700  case ELEC_BUS:
3701  if (src->info->type == ELEC_BATT ||
3702  src->info->type == ELEC_TRU) {
3703  ASSERT(!comp->info->bus.ac);
3704  } else {
3705  ASSERT3U(src_is_AC(src->info), ==, comp->info->bus.ac);
3706  }
3707  network_paint_src_bus(src, upstream, comp, depth);
3708  break;
3709  case ELEC_TRU:
3710  case ELEC_INV:
3711  network_paint_src_tru_inv(src, upstream, comp, depth);
3712  break;
3713  case ELEC_XFRMR:
3714  network_paint_src_xfrmr(src, upstream, comp, depth);
3715  break;
3716  case ELEC_LOAD:
3717  add_src_up(comp, src, upstream);
3718  if (!comp->rw.failed) {
3719  if (comp->rw.in_volts < src->rw.out_volts) {
3720  comp->rw.in_volts = src->rw.out_volts;
3721  comp->rw.in_freq = src->rw.out_freq;
3722  }
3723  } else {
3724  comp->rw.in_volts = 0;
3725  comp->rw.in_freq = 0;
3726  }
3727  break;
3728  case ELEC_CB:
3729  case ELEC_SHUNT:
3730  network_paint_src_scb(src, upstream, comp, depth);
3731  break;
3732  case ELEC_TIE:
3733  network_paint_src_tie(src, upstream, comp, depth);
3734  break;
3735  case ELEC_DIODE:
3736  network_paint_src_diode(src, upstream, comp, depth);
3737  break;
3738  case ELEC_LABEL_BOX:
3739  VERIFY_FAIL();
3740  }
3741 }
3742 
3743 static void
3744 network_paint(elec_sys_t *sys)
3745 {
3746  ASSERT(sys != NULL);
3747  ASSERT_MUTEX_HELD(&sys->worker_interlock);
3748 
3749  for (elec_comp_t *comp = list_head(&sys->gens_batts); comp != NULL;
3750  comp = list_next(&sys->gens_batts, comp)) {
3751  ASSERT(comp->info != NULL);
3752  if ((comp->info->type == ELEC_BATT ||
3753  comp->info->type == ELEC_GEN) && comp->rw.out_volts != 0) {
3754  network_paint_src_comp(comp, comp,
3755  comp->links[0].comp, 0);
3756  }
3757  }
3758 }
3759 
3760 static double
3761 network_load_integrate_tru_inv(const elec_comp_t *src,
3762  const elec_comp_t *upstream, elec_comp_t *comp, unsigned depth, double d_t)
3763 {
3764  ASSERT(src != NULL);
3765  ASSERT(upstream != NULL);
3766  ASSERT(comp != NULL);
3767  ASSERT(comp->links[0].comp != NULL);
3768  ASSERT(comp->links[1].comp != NULL);
3769  ASSERT(comp->info != NULL);
3770  ASSERT(comp->info->type == ELEC_TRU || comp->info->type == ELEC_INV);
3771  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3772 
3773  if (upstream != comp->links[0].comp)
3774  return (0);
3775 
3776  /* When hopping over to the output network, we become the src */
3777  comp->rw.out_amps = network_load_integrate_comp(
3778  comp, comp, comp->links[1].comp, depth + 1, d_t);
3779  if (comp->rw.failed || comp->rw.in_volts == 0) {
3780  comp->tru.prev_amps = 0;
3781  comp->rw.in_amps = 0;
3782  comp->rw.out_amps = 0;
3783  return (0);
3784  }
3785  /*
3786  * Stash the amps value so we can use it to update voltage regulation
3787  * on battery chargers.
3788  */
3789  comp->tru.prev_amps = comp->rw.out_amps;
3790  comp->tru.eff = fx_lin_multi(comp->rw.out_volts * comp->rw.out_amps,
3791  comp->info->tru.eff_curve, true);
3792  ASSERT3F(comp->tru.eff, >, 0);
3793  ASSERT3F(comp->tru.eff, <, 1);
3794  comp->rw.in_amps = ((comp->rw.out_volts / comp->rw.in_volts) *
3795  comp->rw.out_amps) / comp->tru.eff;
3796 
3797  return (comp->rw.in_amps);
3798 }
3799 
3800 static double
3801 network_load_integrate_xfrmr(const elec_comp_t *src,
3802  const elec_comp_t *upstream, elec_comp_t *comp, unsigned depth, double d_t)
3803 {
3804  ASSERT(src != NULL);
3805  ASSERT(upstream != NULL);
3806  ASSERT(comp != NULL);
3807  ASSERT(comp->links[0].comp != NULL);
3808  ASSERT(comp->links[1].comp != NULL);
3809  ASSERT(comp->info != NULL);
3810  ASSERT(comp->info->type == ELEC_XFRMR);
3811  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3812 
3813  if (upstream != comp->links[0].comp)
3814  return (0);
3815 
3816  /* When hopping over to the output network, we become the src */
3817  comp->rw.out_amps = network_load_integrate_comp(
3818  comp, comp, comp->links[1].comp, depth + 1, d_t);
3819  if (comp->rw.failed || comp->rw.in_volts == 0) {
3820  comp->rw.in_amps = 0;
3821  comp->rw.out_amps = 0;
3822  return (0);
3823  }
3824  comp->xfrmr.eff = fx_lin_multi(comp->rw.out_volts * comp->rw.out_amps,
3825  comp->info->xfrmr.eff_curve, true);
3826  ASSERT3F(comp->xfrmr.eff, >, 0);
3827  ASSERT3F(comp->xfrmr.eff, <, 1);
3828  comp->rw.in_amps = ((comp->rw.out_volts / comp->rw.in_volts) *
3829  comp->rw.out_amps) / comp->xfrmr.eff;
3830 
3831  return (comp->rw.in_amps);
3832 }
3833 
3834 static double
3835 network_load_integrate_load(const elec_comp_t *src, elec_comp_t *comp,
3836  unsigned depth, double d_t)
3837 {
3838  double load_WorI, load_I, in_volts_net, incap_I, src_fract;
3839  const elec_comp_info_t *info;
3840 
3841  /* src can be NULL */
3842  ASSERT(comp != NULL);
3843  ASSERT(comp->info != NULL);
3844  ASSERT3U(comp->info->type, ==, ELEC_LOAD);
3845  UNUSED(depth);
3846 
3847  info = comp->info;
3848  /*
3849  * If the input voltage is lower than our input capacitance voltage,
3850  * it will be our input capacitance powering the load, not the input.
3851  */
3852  in_volts_net = MAX(comp->rw.in_volts, comp->load.incap_U);
3853  /*
3854  * Only ask the load if we are receiving sufficient volts.
3855  */
3856  if (in_volts_net >= comp->info->load.min_volts) {
3857  load_WorI = info->load.std_load;
3858  if (info->load.get_load != NULL) {
3859  load_WorI += info->load.get_load(comp,
3860  comp->info->userinfo);
3861  }
3862  } else {
3863  load_WorI = 0;
3864  }
3865  ASSERT3F(load_WorI, >=, 0);
3866 
3867  load_WorI *= comp->load.random_load_factor;
3868  /*
3869  * If the load use a stabilized power supply, the load value is
3870  * in Watts. Calculate the effective current.
3871  */
3872  if (info->load.stab) {
3873  double volts = MAX(in_volts_net, info->load.min_volts);
3874  ASSERT3F(volts, >, 0);
3875  load_I = load_WorI / volts;
3876  } else {
3877  load_I = load_WorI;
3878  }
3879  if (comp->rw.shorted) {
3880  /*
3881  * Shorted components boost their current draw.
3882  */
3883  ASSERT3F(comp->rw.leak_factor, <, 1);
3884  load_I /= (1 - comp->rw.leak_factor);
3885  } else if (comp->rw.failed) {
3886  /*
3887  * Failed components just drop their power consumption to zero
3888  */
3889  load_I = 0;
3890  }
3891  /*
3892  * When the input voltage is greater than the input capacitance
3893  * voltage, we will be charging up the input capacitance.
3894  */
3895  if (info->load.incap_C > 0 &&
3896  comp->rw.in_volts > comp->load.incap_U + 0.01) {
3897  /*
3898  * Capacitor voltage U_c is:
3899  *
3900  * U_c = U_in * (1 - e^(-t / RC))
3901  *
3902  * Where:
3903  * U_in - the input charging voltage
3904  * t - amount of time the cap has been charging in seconds
3905  * R - a series resistance to limit charge current in Ohms
3906  * C - the capacitance in Farad
3907  *
3908  * Since we operate in a fixed time simulation steps, we
3909  * want to instead compute the amount of change in charge
3910  * in one simulation step. So we start with the capacitor
3911  * voltage from the previous simulation loop, U_c_old, and
3912  * subtract it from the input voltage. We can then compute
3913  * the new capacitor voltage using a stepped algorithm:
3914  *
3915  * U_c_new = U_c_old + ((U_in - U_c_old) * (1 - e^(-t / RC)))
3916  */
3917  double U_in = comp->rw.in_volts;
3918  double U_c_old = comp->load.incap_U;
3919  double R = info->load.incap_R;
3920  double C = info->load.incap_C;
3921  double incap_U_new = U_c_old +
3922  ((U_in - U_c_old) * (1 - exp(-d_t / (R * C))));
3923  /*
3924  * Next we convert the previous and new capacitor voltages
3925  * to a total charge value and figure out the net change
3926  * in charge.
3927  *
3928  * Q_old = U_c_old * C
3929  * Q_new = U_c_new * C
3930  * Q_delta = Q_new - Q_old
3931  *
3932  * Where Q_old and Q_new are the old and new capacitor
3933  * charge states (in Coulomb). To calculate the charge
3934  * current, we then simply divide the change in charge
3935  * Q_delta by the time quantum:
3936  *
3937  * I = Q_delta / t
3938  */
3939  double Q_old = comp->load.incap_U * info->load.incap_C;
3940  double Q_new = incap_U_new * info->load.incap_C;
3941  double Q_delta = Q_new - Q_old;
3942 
3943  incap_I = Q_delta / d_t;
3944  } else {
3945  incap_I = 0;
3946  }
3947  /*
3948  * If the input capacitance has a greater voltage than the network
3949  * input voltage, then we need to calculate how much of the charge
3950  * will be provided by the input capacitance. The amount of charge
3951  * Q_load that the load will draw in one time step is:
3952  *
3953  * Q_load = I_load * t
3954  *
3955  * The amount of charge that the capacitor Q_c can provide is
3956  * dependent upon the delta between capacitor charge and network
3957  * input voltage:
3958  *
3959  * Q_c = (U_c - U_in) * C
3960  *
3961  * After draining this amount of charge, the capacitor's voltage
3962  * will be lower than the input voltage and so no more charge can
3963  * be drawn from it.
3964  */
3965  if (comp->load.incap_U > comp->rw.in_volts) {
3966  /* Amount of charge requested by the load in this time step */
3967  double load_Q = load_I * d_t;
3968  /* Amount of charge that can be drawn from the incap */
3969  double avail_Q = (comp->load.incap_U - comp->rw.in_volts) *
3970  info->load.incap_C;
3971  /* Amount of charge actually drawn from the incap */
3972  double used_Q = MIN(load_Q, avail_Q);
3973  /*
3974  * Subtract the charge provided by the incap from the
3975  * network-demanded charge.
3976  */
3977  load_Q -= used_Q;
3978  /*
3979  * Actual network current is the delta vs what the incap
3980  * can provide.
3981  */
3982  comp->rw.in_amps = load_Q / d_t;
3983  comp->rw.out_amps = load_I;
3984  if (comp->load.incap_U >= comp->info->load.min_volts)
3985  comp->rw.out_volts = comp->load.incap_U;
3986  else
3987  comp->rw.out_volts = 0;
3988  comp->load.incap_d_Q = -used_Q;
3989  } else {
3990  /*
3991  * Don't forget to add the input capacitance charging
3992  * current to the network current draw.
3993  */
3994  comp->rw.in_amps = load_I + incap_I;
3995  comp->rw.out_amps = load_I;
3996  comp->rw.out_volts = comp->rw.in_volts;
3997  comp->rw.out_freq = comp->rw.in_freq;
3998  comp->load.incap_d_Q = incap_I * d_t;
3999  }
4000  ASSERT(!isnan(comp->rw.out_amps));
4001  ASSERT(!isnan(comp->rw.out_volts));
4002  comp->load.seen = true;
4003  if (src != NULL) {
4004  src_fract = get_src_fract(comp, src);
4005  comp->links[0].out_amps[src->src_idx] =
4006  NO_NEG_ZERO(-comp->rw.in_amps * src_fract);
4007  } else {
4008  src_fract = 1;
4009  }
4010 
4011  return (comp->rw.in_amps * src_fract);
4012 }
4013 
4014 static double
4015 network_load_integrate_bus(const elec_comp_t *src, const elec_comp_t *upstream,
4016  elec_comp_t *comp, unsigned depth, double d_t)
4017 {
4018  double out_amps_total = 0;
4019 
4020  ASSERT(src != NULL);
4021  ASSERT(comp != NULL);
4022  ASSERT(comp->info != NULL);
4023  ASSERT3U(comp->info->type, ==, ELEC_BUS);
4024  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
4025 
4026  for (unsigned i = 0; i < comp->n_links; i++) {
4027  ASSERT(comp->links[i].comp != NULL);
4028  if (comp->links[i].comp != upstream) {
4029  comp->links[i].out_amps[src->src_idx] =
4030  network_load_integrate_comp(src, comp,
4031  comp->links[i].comp, depth + 1, d_t);
4032  ASSERT3F(comp->links[i].out_amps[src->src_idx], >=, 0);
4033  out_amps_total += comp->links[i].out_amps[src->src_idx];
4034  }
4035  }
4036  out_amps_total /= (1 - comp->rw.leak_factor);
4037  return (out_amps_total);
4038 }
4039 
4040 static double
4041 network_load_integrate_tie(const elec_comp_t *src, const elec_comp_t *upstream,
4042  elec_comp_t *comp, unsigned depth, double d_t)
4043 {
4044  double out_amps_total = 0;
4045 
4046  ASSERT(src != NULL);
4047  ASSERT(comp != NULL);
4048  ASSERT(comp->info != NULL);
4049  ASSERT3U(comp->info->type, ==, ELEC_TIE);
4050  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
4051 
4052  for (unsigned i = 0; i < comp->n_links; i++) {
4053  ASSERT(comp->links[i].comp != NULL);
4054  if (comp->links[i].comp == upstream && !comp->tie.wk_state[i])
4055  return (0);
4056  }
4057  for (unsigned i = 0; i < comp->n_links; i++) {
4058  ASSERT(comp->links[i].comp != NULL);
4059  if (comp->tie.wk_state[i] && comp->links[i].comp != upstream) {
4060  comp->links[i].out_amps[src->src_idx] =
4061  network_load_integrate_comp(src, comp,
4062  comp->links[i].comp, depth + 1, d_t);
4063  ASSERT3F(comp->links[i].out_amps[src->src_idx], >=, 0);
4064  out_amps_total += comp->links[i].out_amps[src->src_idx];
4065  }
4066  }
4067  return (out_amps_total);
4068 }
4069 
4070 static double
4071 network_load_integrate_scb(const elec_comp_t *src, const elec_comp_t *upstream,
4072  elec_comp_t *comp, unsigned depth, double d_t)
4073 {
4074  ASSERT(src != NULL);
4075  ASSERT(upstream != NULL);
4076  ASSERT(comp != NULL);
4077  ASSERT(comp->info != NULL);
4078  ASSERT(comp->info->type == ELEC_CB || comp->info->type == ELEC_SHUNT);
4079  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
4080 
4081  if (!comp->scb.wk_set)
4082  return (0);
4083  for (unsigned i = 0; i < 2; i++) {
4084  if (comp->links[i].comp == upstream) {
4085  comp->links[!i].out_amps[src->src_idx] =
4086  network_load_integrate_comp(src, comp,
4087  comp->links[!i].comp, depth + 1, d_t);
4088  comp->rw.out_amps = sum_link_amps(&comp->links[0]) -
4089  sum_link_amps(&comp->links[1]);
4090  comp->rw.out_amps = NO_NEG_ZERO(ABS(comp->rw.out_amps));
4091  comp->rw.in_amps = comp->rw.out_amps;
4092 
4093  return (comp->links[!i].out_amps[src->src_idx]);
4094  }
4095  }
4096  VERIFY_FAIL();
4097 }
4098 
4099 static double
4100 network_load_integrate_batt(const elec_comp_t *src,
4101  const elec_comp_t *upstream, elec_comp_t *batt, unsigned depth, double d_t)
4102 {
4103  ASSERT(src != NULL);
4104  ASSERT(batt != NULL);
4105  ASSERT(batt->info != NULL);
4106  ASSERT3U(batt->info->type, ==, ELEC_BATT);
4107 
4108  if (depth != 0 && check_upstream(batt, src, upstream)) {
4109  double U_delta = MAX(src->rw.out_volts - batt->rw.out_volts, 0);
4110 
4111  ASSERT0(src->rw.out_freq);
4112  if (batt->batt.chg_rel < 1) {
4113  double R = batt->info->batt.chg_R /
4114  (1 - batt->batt.chg_rel);
4115  batt->rw.in_volts = src->rw.out_volts;
4116  batt->rw.in_amps = U_delta / R;
4117  /*
4118  * Store the charging rate so network_update_batt can
4119  * incorporate it into its battery energy state
4120  * calculation.
4121  */
4122  batt->batt.rechg_W = batt->rw.in_volts *
4123  batt->rw.in_amps;
4124  }
4125  batt->rw.out_amps = 0;
4126 
4127  return (batt->rw.in_amps);
4128  } else if (depth == 0) {
4129  batt->rw.out_amps = network_load_integrate_comp(batt, batt,
4130  batt->links[0].comp, depth + 1, d_t);
4131  batt->batt.prev_amps = batt->rw.out_amps;
4132 
4133  return (batt->rw.out_amps);
4134  } else {
4135  return (0);
4136  }
4137 }
4138 
4139 static double
4140 network_load_integrate_gen(elec_comp_t *gen, unsigned depth, double d_t)
4141 {
4142  double out_pwr;
4143 
4144  ASSERT(gen != NULL);
4145  ASSERT(gen->info != NULL);
4146  ASSERT3U(gen->info->type, ==, ELEC_GEN);
4147 
4148  if (depth != 0)
4149  return (0);
4150 
4151  gen->rw.out_amps = network_load_integrate_comp(gen, gen,
4152  gen->links[0].comp, depth + 1, d_t);
4153  gen->rw.in_volts = gen->rw.out_volts;
4154  gen->rw.in_freq = gen->rw.out_freq;
4155  out_pwr = gen->rw.in_volts * gen->rw.out_amps;
4156  gen->gen.eff = fx_lin_multi(out_pwr, gen->info->gen.eff_curve, true);
4157  gen->rw.in_amps = gen->rw.out_amps / gen->gen.eff;
4158 
4159  return (gen->rw.out_amps);
4160 }
4161 
4162 static double
4163 network_load_integrate_diode(const elec_comp_t *src,
4164  const elec_comp_t *upstream, elec_comp_t *comp, unsigned depth, double d_t)
4165 {
4166  ASSERT(src != NULL);
4167  ASSERT(upstream != NULL);
4168  ASSERT(comp != NULL);
4169  ASSERT3P(upstream, ==, comp->links[0].comp);
4170  ASSERT3F(d_t, >, 0);
4171  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
4172 
4173  comp->links[1].out_amps[src->src_idx] = network_load_integrate_comp(
4174  src, comp, comp->links[1].comp, depth + 1, d_t);
4175  comp->rw.out_amps = sum_link_amps(&comp->links[1]);
4176  comp->rw.in_amps = comp->rw.out_amps;
4177  ASSERT(!isnan(comp->rw.in_amps));
4178 
4179  return (comp->links[1].out_amps[src->src_idx]);
4180 }
4181 
4182 static double
4183 network_load_integrate_comp(const elec_comp_t *src,
4184  const elec_comp_t *upstream, elec_comp_t *comp, unsigned depth, double d_t)
4185 {
4186  ASSERT(src != NULL);
4187  ASSERT(src->info != NULL);
4188  ASSERT(src->info->type == ELEC_BATT || src->info->type == ELEC_GEN ||
4189  src->info->type == ELEC_TRU || src->info->type == ELEC_INV ||
4190  src->info->type == ELEC_XFRMR);
4191  ASSERT(comp != NULL);
4192  ASSERT(comp->info != NULL);
4193  ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
4194  ASSERT3F(d_t, >, 0);
4195 
4196  if (comp != src && !check_upstream(comp, src, upstream))
4197  return (0);
4198 
4199  switch (comp->info->type) {
4200  case ELEC_BATT:
4201  return (network_load_integrate_batt(src, upstream, comp,
4202  depth, d_t));
4203  case ELEC_GEN:
4204  return (network_load_integrate_gen(comp, depth, d_t));
4205  case ELEC_TRU:
4206  case ELEC_INV:
4207  ASSERT3P(upstream, ==, comp->links[0].comp);
4208  return (network_load_integrate_tru_inv(src, upstream, comp,
4209  depth, d_t));
4210  case ELEC_XFRMR:
4211  ASSERT3P(upstream, ==, comp->links[0].comp);
4212  return (network_load_integrate_xfrmr(src, upstream, comp,
4213  depth, d_t));
4214  case ELEC_LOAD:
4215  return (network_load_integrate_load(src, comp, depth, d_t));
4216  case ELEC_BUS:
4217  return (network_load_integrate_bus(src, upstream, comp,
4218  depth, d_t));
4219  case ELEC_CB:
4220  case ELEC_SHUNT:
4221  return (network_load_integrate_scb(src, upstream, comp,
4222  depth, d_t));
4223  case ELEC_TIE:
4224  return (network_load_integrate_tie(src, upstream, comp,
4225  depth, d_t));
4226  case ELEC_DIODE:
4227  return (network_load_integrate_diode(src, upstream, comp,
4228  depth, d_t));
4229  case ELEC_LABEL_BOX:
4230  VERIFY_FAIL();
4231  }
4232  VERIFY_FAIL();
4233 }
4234 
4235 static void
4236 network_load_integrate(elec_sys_t *sys, double d_t)
4237 {
4238  ASSERT(sys != NULL);
4239  ASSERT3F(d_t, >, 0);
4240 
4241  for (elec_comp_t *comp = list_head(&sys->gens_batts); comp != NULL;
4242  comp = list_next(&sys->gens_batts, comp)) {
4243  comp->rw.out_amps = network_load_integrate_comp(comp, comp,
4244  comp, 0, d_t);
4245  }
4246 }
4247 
4248 static void
4249 network_state_xfer(elec_sys_t *sys)
4250 {
4251  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
4252  comp = list_next(&sys->comps, comp)) {
4253  mutex_enter(&comp->rw_ro_lock);
4254  /* Copy in caller-side settings that might have been changed */
4255  if (comp->ro.failed != comp->rw.failed)
4256  comp->rw.failed = comp->ro.failed;
4257  if (comp->ro.shorted != comp->rw.shorted)
4258  comp->rw.shorted = comp->ro.shorted;
4259  comp->ro = comp->rw;
4260  mutex_exit(&comp->rw_ro_lock);
4261  }
4262 }
4263 
4264 static void
4265 mk_spaces(char *spaces, unsigned len)
4266 {
4267  memset(spaces, 0, len);
4268  for (unsigned i = 0; i + 1 < len; i += 2) {
4269  spaces[i] = '|';
4270  if (i + 3 < len)
4271  spaces[i + 1] = ' ';
4272  else
4273  spaces[i + 1] = '-';
4274  }
4275 }
4276 
4277 static void
4278 print_trace_data(const elec_comp_t *comp, unsigned depth, bool out_data,
4279  double load)
4280 {
4281  char *spaces;
4282  double W;
4283 
4284  ASSERT(comp != NULL);
4285 
4286  spaces = safe_malloc(2 * depth + 1);
4287  mk_spaces(spaces, 2 * depth + 1);
4288  W = out_data ? (comp->rw.out_volts * comp->rw.out_amps) :
4289  (comp->rw.in_volts * comp->rw.in_amps);
4290  logMsg("%s%-5s %s %3s: %.2fW LOADS: %.2fW",
4291  spaces, comp_type2str(comp->info->type), comp->info->name,
4292  out_data ? "OUT" : "IN", W, load);
4293  free(spaces);
4294 }
4295 
4296 static double
4297 network_trace_scb(const elec_comp_t *upstream, const elec_comp_t *comp,
4298  unsigned depth, bool do_print)
4299 {
4300  ASSERT(upstream != NULL);
4301  ASSERT(comp != NULL);
4302  ASSERT(comp->info->type == ELEC_CB || comp->info->type == ELEC_SHUNT);
4303 
4304  if (!comp->scb.wk_set)
4305  return (0);
4306  if (upstream == comp->links[0].comp) {
4307  return (network_trace(comp, comp->links[1].comp,
4308  depth + 1, do_print));
4309  } else {
4310  return (network_trace(comp, comp->links[0].comp,
4311  depth + 1, do_print));
4312  }
4313 }
4314 
4315 static double
4316 network_trace(const elec_comp_t *upstream, const elec_comp_t *comp,
4317  unsigned depth, bool do_print)
4318 {
4319  double load_trace = 0;
4320 
4321  ASSERT(upstream != NULL);
4322  ASSERT(comp != NULL);
4323 
4324  switch (comp->info->type) {
4325  case ELEC_BATT:
4326  load_trace = network_trace(comp, comp->links[0].comp, depth + 1,
4327  false);
4328  load_trace += comp->rw.out_volts * comp->rw.in_amps;
4329  if (do_print) {
4330  if (upstream == comp) {
4331  print_trace_data(comp, depth, true, load_trace);
4332  } else {
4333  print_trace_data(comp, depth, false,
4334  load_trace);
4335  }
4336  network_trace(comp, comp->links[0].comp, depth + 1,
4337  true);
4338  }
4339  break;
4340  case ELEC_GEN:
4341  load_trace = network_trace(comp, comp->links[0].comp,
4342  depth + 1, false);
4343  if (do_print) {
4344  print_trace_data(comp, depth, true, load_trace);
4345  network_trace(comp, comp->links[0].comp, depth + 1,
4346  true);
4347  }
4348  break;
4349  case ELEC_TRU:
4350  case ELEC_INV:
4351  case ELEC_XFRMR:
4352  if (upstream != comp) {
4353  if (do_print)
4354  print_trace_data(comp, depth, false, 0);
4355  return (comp->rw.in_volts * comp->rw.in_amps);
4356  } else {
4357  load_trace = network_trace(comp,
4358  comp->links[0].comp, depth + 1, false);
4359  if (do_print) {
4360  print_trace_data(comp, depth, true, load_trace);
4361  network_trace(comp, comp->links[0].comp,
4362  depth + 1, true);
4363  }
4364  }
4365  break;
4366  case ELEC_LOAD:
4367  if (do_print)
4368  print_trace_data(comp, depth, false, 0);
4369  return (comp->rw.in_volts * comp->rw.in_amps);
4370  case ELEC_BUS:
4371  for (unsigned i = 0; i < comp->n_links; i++) {
4372  load_trace += network_trace(comp, comp->links[i].comp,
4373  depth + 1, false);
4374  }
4375  if (do_print) {
4376  print_trace_data(comp, depth, false, load_trace);
4377  for (unsigned i = 0; i < comp->n_links; i++) {
4378  network_trace(comp, comp->links[i].comp,
4379  depth + 1, true);
4380  }
4381  }
4382  break;
4383  case ELEC_CB:
4384  case ELEC_SHUNT:
4385  load_trace = network_trace_scb(upstream, comp, depth, false);
4386  if (do_print) {
4387  print_trace_data(comp, depth, false, load_trace);
4388  network_trace_scb(upstream, comp, depth, true);
4389  }
4390  break;
4391  case ELEC_TIE:
4392  for (unsigned i = 0; i < comp->n_links; i++) {
4393  if (comp->tie.wk_state[i]) {
4394  load_trace += network_trace(comp,
4395  comp->links[i].comp, depth + 1, false);
4396  }
4397  }
4398  if (do_print) {
4399  print_trace_data(comp, depth, false, load_trace);
4400  for (unsigned i = 0; i < comp->n_links; i++) {
4401  if (comp->tie.wk_state[i]) {
4402  load_trace += network_trace(comp,
4403  comp->links[i].comp, depth + 1,
4404  true);
4405  }
4406  }
4407  }
4408  break;
4409  case ELEC_DIODE:
4410  load_trace = network_trace(comp, comp->links[1].comp,
4411  depth + 1, false);
4412  if (do_print) {
4413  print_trace_data(comp, depth, false, load_trace);
4414  network_trace(comp, comp->links[1].comp,
4415  depth + 1, true);
4416  }
4417  break;
4418  default:
4419  VERIFY_FAIL();
4420  }
4421 
4422  return (load_trace);
4423 }
4424 
4425 static bool_t
4426 elec_sys_worker(void *userinfo)
4427 {
4428  elec_sys_t *sys;
4429  uint64_t now = microclock();
4430  double d_t;
4431 #ifdef LIBELEC_TIMING_DEBUG
4432  static int steps = 0;
4433  static double d_t_total = 0;
4434  static uint64_t last_report = 0;
4435 #endif /* defined(LIBELEC_TIMING_DEBUG) */
4436 
4437  ASSERT(userinfo != NULL);
4438  sys = userinfo;
4439 
4440  mutex_enter(&sys->paused_lock);
4441  if (sys->paused || sys->prev_clock == 0) {
4442  mutex_exit(&sys->paused_lock);
4443  sys->prev_clock = now;
4444  return (B_TRUE);
4445  }
4446  d_t = USEC2SEC(now - sys->prev_clock) * sys->time_factor;
4447  sys->prev_clock = now;
4448  mutex_exit(&sys->paused_lock);
4449 
4450 #ifdef LIBELEC_TIMING_DEBUG
4451  steps++;
4452  d_t_total += d_t;
4453  if (now - last_report > SEC2USEC(1)) {
4454  logMsg("steps: %4d d_t_avg: %f", steps, d_t_total / steps);
4455  steps = 0;
4456  d_t_total = 0;
4457  last_report = now;
4458  }
4459 #endif /* defined(LIBELEC_TIMING_DEBUG) */
4460 
4461  /*
4462  * In net-recv mode, we only listen in for updates to our requested
4463  * endpoints and nothing else.
4464  */
4465 #ifdef LIBELEC_WITH_NETLINK
4466  if (sys->net_recv.active) {
4467  elec_net_recv_update(sys);
4468  return (true);
4469  }
4470 #endif /* defined(LIBELEC_WITH_NETLINK) */
4471  mutex_enter(&sys->worker_interlock);
4472 
4473  mutex_enter(&sys->user_cbs_lock);
4474  for (user_cb_info_t *ucbi = avl_first(&sys->user_cbs); ucbi != NULL;
4475  ucbi = AVL_NEXT(&sys->user_cbs, ucbi)) {
4476  if (ucbi->pre) {
4477  ASSERT(ucbi->cb != NULL);
4478  ucbi->cb(sys, true, ucbi->userinfo);
4479  }
4480  }
4481  mutex_exit(&sys->user_cbs_lock);
4482 
4483  network_reset(sys, d_t);
4484  network_srcs_update(sys, d_t);
4485  network_loads_randomize(sys, d_t);
4486  network_paint(sys);
4487  network_load_integrate(sys, d_t);
4488  network_loads_update(sys, d_t);
4489  network_ties_update(sys);
4490  /*
4491  * Must occur AFTER the integrity check! network_state_xfer touches
4492  * the rw state and syncs it to the ro state.
4493  */
4494  network_state_xfer(sys);
4495 
4496  mutex_enter(&sys->user_cbs_lock);
4497  for (user_cb_info_t *ucbi = avl_first(&sys->user_cbs); ucbi != NULL;
4498  ucbi = AVL_NEXT(&sys->user_cbs, ucbi)) {
4499  if (!ucbi->pre) {
4500  ASSERT(ucbi->cb != NULL);
4501  ucbi->cb(sys, false, ucbi->userinfo);
4502  }
4503  }
4504  mutex_exit(&sys->user_cbs_lock);
4505 
4506  mutex_exit(&sys->worker_interlock);
4507 
4508 #ifdef LIBELEC_WITH_NETLINK
4509  elec_net_send_update(sys);
4510 #endif
4511  return (true);
4512 }
4513 
4514 static void
4515 comp_free(elec_comp_t *comp)
4516 {
4517  ASSERT(comp != NULL);
4518  ASSERT(comp->info);
4519 
4520 #ifdef LIBELEC_WITH_DRS
4521  dr_delete(&comp->drs.in_volts);
4522  dr_delete(&comp->drs.out_volts);
4523  dr_delete(&comp->drs.in_amps);
4524  dr_delete(&comp->drs.out_amps);
4525  dr_delete(&comp->drs.in_pwr);
4526  dr_delete(&comp->drs.out_pwr);
4527 #endif /* defined(LIBELEC_WITH_DRS) */
4528 
4529  if (comp->info->type == ELEC_BATT)
4530  mutex_destroy(&comp->batt.lock);
4531  else if (comp->info->type == ELEC_GEN)
4532  mutex_destroy(&comp->gen.lock);
4533 
4534  ZERO_FREE_N(comp->links, comp->n_links);
4535  if (comp->info->type == ELEC_TIE) {
4536  free(comp->tie.cur_state);
4537  free(comp->tie.wk_state);
4538  mutex_destroy(&comp->tie.lock);
4539  }
4540  mutex_destroy(&comp->rw_ro_lock);
4541 
4542  memset(comp, 0, sizeof (*comp));
4543  free(comp);
4544 }
4545 
4550 void
4551 libelec_cb_set(elec_comp_t *comp, bool set)
4552 {
4553  ASSERT(comp != NULL);
4554  ASSERT(comp->info != NULL);
4555  ASSERT3U(comp->info->type, ==, ELEC_CB);
4556  /*
4557  * This is atomic, no locking required. Also, the worker
4558  * copies its the breaker state to wk_set at the start.
4559  */
4560  comp->scb.cur_set = set;
4561 }
4562 
4568 bool
4569 libelec_cb_get(const elec_comp_t *comp)
4570 {
4571  ASSERT(comp != NULL);
4572  ASSERT(comp->info != NULL);
4573  ASSERT3U(comp->info->type, ==, ELEC_CB);
4574  /* atomic read, no need to lock */
4575  return (comp->scb.cur_set);
4576 }
4577 
4583 double
4584 libelec_cb_get_temp(const elec_comp_t *comp)
4585 {
4586  ASSERT(comp != NULL);
4587  ASSERT(comp->info != NULL);
4588  ASSERT3U(comp->info->type, ==, ELEC_CB);
4589  /* atomic read, no need to lock */
4590  return (comp->scb.temp);
4591 }
4592 
4604 void
4605 libelec_tie_set_list(elec_comp_t *comp, size_t list_len,
4606  elec_comp_t *const bus_list[STATIC_ARRAY_LEN_ARG(list_len)])
4607 {
4608  bool *new_state;
4609 
4610  ASSERT(comp != NULL);
4611  ASSERT(comp->info != NULL);
4612  ASSERT3U(comp->info->type, ==, ELEC_TIE);
4613  ASSERT(bus_list != NULL || list_len == 0);
4614 
4615  /* A failure of a tie means it gets stuck in its current position */
4616  if (comp->ro.failed)
4617  return;
4618 
4619  if (list_len == 0) {
4620  mutex_enter(&comp->tie.lock);
4621  memset(comp->tie.cur_state, 0,
4622  comp->n_links * sizeof (*comp->tie.cur_state));
4623  mutex_exit(&comp->tie.lock);
4624  return;
4625  }
4626 
4627  /* The buslist is immutable, so no need to lock */
4628  new_state = safe_calloc(comp->n_links, sizeof (*new_state));
4629  for (size_t i = 0; i < list_len; i++) {
4630  size_t j;
4631  for (j = 0; j < comp->n_links; j++) {
4632  if (comp->links[j].comp == bus_list[i]) {
4633  new_state[j] = true;
4634  break;
4635  }
4636  }
4637  ASSERT_MSG(j != comp->n_links,
4638  "Tie %s is not connected to bus %s", comp->info->name,
4639  bus_list[i]->info->name);
4640  }
4641 
4642  mutex_enter(&comp->tie.lock);
4643  memcpy(comp->tie.cur_state, new_state,
4644  comp->n_links * sizeof (*comp->tie.cur_state));
4645  mutex_exit(&comp->tie.lock);
4646 
4647  free(new_state);
4648 }
4649 
4658 void
4659 libelec_tie_set(elec_comp_t *comp, ...)
4660 {
4661  va_list ap;
4662 
4663  ASSERT(comp != NULL);
4664 
4665  va_start(ap, comp);
4666  libelec_tie_set_v(comp, ap);
4667  va_end(ap);
4668 }
4669 
4675 void
4676 libelec_tie_set_v(elec_comp_t *comp, va_list ap)
4677 {
4678  va_list ap2;
4679  elec_comp_t **bus_list;
4680  size_t len;
4681 
4682  ASSERT(comp != NULL);
4683  ASSERT(comp->sys != NULL);
4684  ASSERT(comp->info != NULL);
4685  ASSERT3U(comp->info->type, ==, ELEC_TIE);
4686 
4687  if (comp->ro.failed)
4688  return;
4689 
4690  va_copy(ap2, ap);
4691  for (len = 0; va_arg(ap2, elec_comp_t *) != NULL; len++)
4692  ;
4693  va_end(ap2);
4694 
4695  bus_list = safe_malloc(sizeof (*bus_list) * len);
4696  for (size_t i = 0; i < len; i++)
4697  bus_list[i] = va_arg(ap, elec_comp_t *);
4698  libelec_tie_set_list(comp, len, bus_list);
4699  free(bus_list);
4700 }
4701 
4712 void
4713 libelec_tie_set_all(elec_comp_t *comp, bool tied)
4714 {
4715  ASSERT(comp != NULL);
4716  ASSERT(comp->info != NULL);
4717  ASSERT3U(comp->info->type, ==, ELEC_TIE);
4718 
4719  if (comp->ro.failed)
4720  return;
4721 
4722  mutex_enter(&comp->tie.lock);
4723  for (unsigned i = 0; i < comp->n_links; i++)
4724  comp->tie.cur_state[i] = tied;
4725  mutex_exit(&comp->tie.lock);
4726 }
4727 
4737 bool
4738 libelec_tie_get_all(elec_comp_t *comp)
4739 {
4740  bool tied = true;
4741 
4742  ASSERT(comp != NULL);
4743  ASSERT(comp->info != NULL);
4744  ASSERT3U(comp->info->type, ==, ELEC_TIE);
4745 
4746  mutex_enter(&comp->tie.lock);
4747  for (unsigned i = 0; tied && i < comp->n_links; i++)
4748  tied &= comp->tie.cur_state[i];
4749  mutex_exit(&comp->tie.lock);
4750 
4751  return (tied);
4752 }
4753 
4767 size_t
4768 libelec_tie_get_list(elec_comp_t *comp, size_t cap,
4769  elec_comp_t *bus_list[STATIC_ARRAY_LEN_ARG(cap)])
4770 {
4771  size_t n_tied = 0;
4772 
4773  ASSERT(comp != NULL);
4774  ASSERT(comp->info != NULL);
4775  ASSERT3U(comp->info->type, ==, ELEC_TIE);
4776  ASSERT3U(cap, >=, comp->n_links);
4777  ASSERT(bus_list != NULL);
4778 
4779  mutex_enter(&comp->tie.lock);
4780  for (unsigned i = 0; i < comp->n_links; i++) {
4781  if (comp->tie.cur_state[i])
4782  bus_list[n_tied++] = comp->links[i].comp;
4783  }
4784  mutex_exit(&comp->tie.lock);
4785 
4786  return (n_tied);
4787 }
4788 
4798 size_t
4799 libelec_tie_get_num_buses(const elec_comp_t *comp)
4800 {
4801  ASSERT(comp != NULL);
4802  ASSERT(comp->info != NULL);
4803  ASSERT3U(comp->info->type, ==, ELEC_TIE);
4804  /* This is immutable, so no need to lock */
4805  return (comp->n_links);
4806 }
4807 
4824 bool
4825 libelec_tie_get(elec_comp_t *tie, bool_t exhaustive, ...)
4826 {
4827  va_list ap;
4828  bool res;
4829 
4830  ASSERT(tie != NULL);
4831  ASSERT3U(tie->info->type, ==, ELEC_TIE);
4832  va_start(ap, exhaustive);
4833  res = libelec_tie_get_v(tie, exhaustive, ap);
4834  va_end(ap);
4835 
4836  return (res);
4837 }
4838 
4843 bool
4844 libelec_tie_get_v(elec_comp_t *tie, bool exclusive, va_list ap)
4845 {
4846  bool res = true;
4847  size_t list_len = 0, n_tied = 0;
4848  const elec_comp_t *bus;
4849 
4850  ASSERT(tie != NULL);
4851  ASSERT3U(tie->info->type, ==, ELEC_TIE);
4852 
4853  mutex_enter(&tie->tie.lock);
4854  for (unsigned i = 0; i < tie->n_links; i++) {
4855  if (tie->tie.cur_state[i])
4856  n_tied++;
4857  }
4858  for (list_len = 0; (bus = va_arg(ap, const elec_comp_t *)) != NULL;
4859  list_len++) {
4860  bool found = false;
4861  for (unsigned i = 0; i < tie->n_links; i++) {
4862  if (tie->links[i].comp == bus) {
4863  found = true;
4864  if (!tie->tie.cur_state[i]) {
4865  res = false;
4866  goto out;
4867  }
4868  break;
4869  }
4870  }
4871  ASSERT(found);
4872  }
4873  if (list_len != n_tied && exclusive)
4874  res = false;
4875 out:
4876  mutex_exit(&tie->tie.lock);
4877 
4878  return (res);
4879 }
4880 
4892 void
4893 libelec_gen_set_rpm(elec_comp_t *gen, double rpm)
4894 {
4895  ASSERT(gen != NULL);
4896  ASSERT(gen->info != NULL);
4897  ASSERT3U(gen->info->type, ==, ELEC_GEN);
4898  ASSERT_MSG(gen->info->gen.get_rpm == NULL,
4899  "Attempted to call libelec_gen_set_rpm() for generator %s, "
4900  "however this generator already had an rpm callback set using "
4901  "libelec_gen_set_rpm_cb(). You may NOT mix both mechanisms for "
4902  "setting a generator's speed. Either set the speed directly "
4903  "using libelec_gen_set_rpm() -OR- use the callback method "
4904  "using libelec_gen_set_rpm_cb(), but not both.", gen->info->name);
4905  mutex_enter(&gen->gen.lock);
4906  gen->gen.rpm = rpm;
4907  mutex_exit(&gen->gen.lock);
4908 }
4909 
4919 double
4920 libelec_gen_get_rpm(const elec_comp_t *gen)
4921 {
4922  double rpm;
4923  ASSERT(gen != NULL);
4924  ASSERT(gen->info != NULL);
4925  ASSERT3U(gen->info->type, ==, ELEC_GEN);
4926  mutex_enter(&((elec_comp_t *)gen)->gen.lock);
4927  rpm = gen->gen.rpm;
4928  mutex_exit(&((elec_comp_t *)gen)->gen.lock);
4929  return (rpm);
4930 }
4931 
4937 double
4938 libelec_batt_get_chg_rel(const elec_comp_t *batt)
4939 {
4940  ASSERT(batt != NULL);
4941  ASSERT(batt->info != NULL);
4942  ASSERT3U(batt->info->type, ==, ELEC_BATT);
4943  return (batt->batt.chg_rel);
4944 }
4945 
4952 void
4953 libelec_batt_set_chg_rel(elec_comp_t *batt, double chg_rel)
4954 {
4955  ASSERT(batt != NULL);
4956  ASSERT(batt->info != NULL);
4957  ASSERT3U(batt->info->type, ==, ELEC_BATT);
4958  ASSERT3F(chg_rel, >=, 0);
4959  ASSERT3F(chg_rel, <=, 1);
4960 
4961  mutex_enter(&batt->sys->worker_interlock);
4962  batt->batt.chg_rel = chg_rel;
4963  /* To prevent over-charging if the previous cycle was charging */
4964  batt->batt.rechg_W = 0;
4965  mutex_exit(&batt->sys->worker_interlock);
4966 }
4967 
4973 double
4974 libelec_batt_get_temp(const elec_comp_t *batt)
4975 {
4976  double T;
4977  ASSERT(batt != NULL);
4978  ASSERT(batt->info != NULL);
4979  ASSERT3U(batt->info->type, ==, ELEC_BATT);
4980  mutex_enter(&((elec_comp_t *)batt)->batt.lock);
4981  T = batt->batt.T;
4982  mutex_exit(&((elec_comp_t *)batt)->batt.lock);
4983  return (T);
4984 }
4985 
4993 void
4994 libelec_batt_set_temp(elec_comp_t *batt, double T)
4995 {
4996  ASSERT(batt != NULL);
4997  ASSERT(batt->info != NULL);
4998  ASSERT3U(batt->info->type, ==, ELEC_BATT);
4999  ASSERT3F(T, >, 0);
5000  mutex_enter(&batt->batt.lock);
5001  batt->batt.T = T;
5002  mutex_exit(&batt->batt.lock);
5003 }
5004 
5008 bool
5009 libelec_chgr_get_working(const elec_comp_t *chgr)
5010 {
5011  ASSERT(chgr != NULL);
5012  ASSERT(chgr->info != NULL);
5013  ASSERT3U(chgr->info->type, ==, ELEC_TRU);
5014  ASSERT(chgr->info->tru.charger);
5015  ASSERT(chgr->tru.batt_conn != NULL);
5016  return (chgr->ro.in_volts > 90 &&
5017  libelec_tie_get_all(chgr->tru.batt_conn));
5018 }
5019 
5032 double
5033 libelec_phys_get_batt_voltage(double U_nominal, double chg_rel, double I_rel)
5034 {
5035  static const vect2_t chg_volt_curve[] = {
5036  {0.00, 0.00},
5037  {0.04, 0.70},
5038  {0.10, 0.80},
5039  {0.20, 0.87},
5040  {0.30, 0.91},
5041  {0.45, 0.94},
5042  {0.60, 0.95},
5043  {0.80, 0.96},
5044  {0.90, 0.97},
5045  {1.00, 1.00}
5046  };
5047  ASSERT3F(U_nominal, >, 0);
5048  ASSERT3F(chg_rel, >=, 0);
5049  /*
5050  * Small numerical precision errors during a state restore can cause
5051  * this value to go slightly over '1'. Ignore those cases.
5052  */
5053  ASSERT3F(chg_rel, <=, 1.0001);
5054  I_rel = clamp(I_rel, 0, 1);
5055  return (U_nominal * (1 - clamp(pow(I_rel, 1.45), 0, 1)) *
5056  fx_lin_multi2(chg_rel, chg_volt_curve,
5057  ARRAY_NUM_ELEM(chg_volt_curve), true));
5058 }
5059 
5060 #ifdef LIBELEC_WITH_NETLINK
5061 
5062 static void
5063 kill_conn(elec_sys_t *sys, net_conn_t *conn)
5064 {
5065  ASSERT(sys != NULL);
5066  ASSERT_MUTEX_HELD(&sys->worker_interlock);
5067  ASSERT(conn != NULL);
5068 
5069  list_remove(&sys->net_send.conns_list, conn);
5070  htbl_remove(&sys->net_send.conns, &conn->conn_id, false);
5071 
5072  free(conn->map);
5073  ZERO_FREE(conn);
5074 }
5075 
5076 static void
5077 conn_add_notif(netlink_conn_id_t conn_id, netlink_conn_ev_t ev, void *userinfo)
5078 {
5079  elec_sys_t *sys;
5080 
5081  UNUSED(conn_id);
5082  UNUSED(ev);
5083  ASSERT(userinfo != NULL);
5084  sys = userinfo;
5085 
5086  mutex_enter(&sys->worker_interlock);
5087  if (sys->net_recv.active && sys->started)
5088  send_net_recv_map(sys);
5089  mutex_exit(&sys->worker_interlock);
5090 }
5091 
5092 static void
5093 conn_rem_notif(netlink_conn_id_t conn_id, netlink_conn_ev_t ev,
5094  void *userinfo)
5095 {
5096  elec_sys_t *sys;
5097  net_conn_t *conn;
5098 
5099  UNUSED(ev);
5100  ASSERT(userinfo != NULL);
5101  sys = userinfo;
5102 
5103  mutex_enter(&sys->worker_interlock);
5104  conn = htbl_lookup(&sys->net_send.conns, &conn_id);
5105  if (conn != NULL)
5106  kill_conn(sys, conn);
5107  mutex_exit(&sys->worker_interlock);
5108 }
5109 
5110 void
5111 libelec_enable_net_send(elec_sys_t *sys)
5112 {
5113  ASSERT(sys != NULL);
5114  ASSERT(!sys->started);
5115  ASSERT(!sys->net_send.active);
5116  ASSERT(!sys->net_recv.active);
5117 
5118  sys->net_send.active = true;
5119  sys->net_send.xmit_ctr = 0;
5120  htbl_create(&sys->net_send.conns, 128, sizeof (netlink_conn_id_t),
5121  false);
5122  list_create(&sys->net_send.conns_list, sizeof (net_conn_t),
5123  offsetof(net_conn_t, node));
5124 
5125  sys->net_send.proto.proto_id = NETLINK_PROTO_LIBELEC;
5126  sys->net_send.proto.name = "libelec";
5127  sys->net_send.proto.msg_rcvd_notif = netlink_send_msg_notif;
5128  sys->net_send.proto.conn_rem_notif = conn_rem_notif;
5129  sys->net_send.proto.userinfo = sys;
5130  netlink_add_proto(&sys->net_send.proto);
5131 }
5132 
5133 static void
5134 net_conn_free(void *net_conn, void *unused)
5135 {
5136  net_conn_t *nc;
5137 
5138  ASSERT(net_conn != NULL);
5139  nc = net_conn;
5140  UNUSED(unused);
5141  free(nc->map);
5142  ZERO_FREE(nc);
5143 }
5144 
5145 void
5146 libelec_disable_net_send(elec_sys_t *sys)
5147 {
5148  ASSERT(sys != NULL);
5149  ASSERT(!sys->started);
5150 
5151  if (sys->net_send.active) {
5152  netlink_remove_proto(&sys->net_send.proto);
5153  while (list_remove_head(&sys->net_send.conns_list) != NULL)
5154  ;
5155  list_destroy(&sys->net_send.conns_list);
5156  htbl_empty(&sys->net_send.conns, net_conn_free, NULL);
5157  htbl_destroy(&sys->net_send.conns);
5158  sys->net_send.active = false;
5159  }
5160 }
5161 
5162 void
5163 libelec_enable_net_recv(elec_sys_t *sys)
5164 {
5165  ASSERT(sys != NULL);
5166  ASSERT(!sys->started);
5167  ASSERT(!sys->net_send.active);
5168  ASSERT(!sys->net_recv.active);
5169 
5170  sys->net_recv.map = safe_calloc(NETMAPSZ(sys),
5171  sizeof (*sys->net_recv.map));
5172  sys->net_recv.map_dirty = false;
5173  sys->net_recv.active = true;
5174 
5175  sys->net_recv.proto.proto_id = NETLINK_PROTO_LIBELEC;
5176  sys->net_recv.proto.name = "libelec";
5177  sys->net_recv.proto.msg_rcvd_notif = netlink_recv_msg_notif;
5178  sys->net_recv.proto.conn_add_notif = conn_add_notif;
5179  sys->net_recv.proto.userinfo = sys;
5180 
5181  netlink_add_proto(&sys->net_recv.proto);
5182 }
5183 
5184 void
5185 libelec_disable_net_recv(elec_sys_t *sys)
5186 {
5187  ASSERT(sys != NULL);
5188  ASSERT(!sys->started);
5189  if (sys->net_recv.active) {
5190  netlink_remove_proto(&sys->net_recv.proto);
5191  free(sys->net_recv.map);
5192  sys->net_recv.map = NULL;
5193  sys->net_recv.active = false;
5194  }
5195 }
5196 
5197 static net_conn_t *
5198 get_net_conn(elec_sys_t *sys, netlink_conn_id_t conn_id)
5199 {
5200  net_conn_t *conn;
5201 
5202  ASSERT(sys != NULL);
5203  ASSERT_MUTEX_HELD(&sys->worker_interlock);
5204 
5205  conn = htbl_lookup(&sys->net_send.conns, &conn_id);
5206  if (conn == NULL) {
5207  conn = safe_calloc(1, sizeof (*conn));
5208  conn->conn_id = conn_id;
5209  conn->map = safe_calloc(NETMAPSZ(sys), sizeof (*conn->map));
5210  delay_line_init(&conn->kill_delay, SEC2USEC(20));
5211  htbl_set(&sys->net_send.conns, &conn_id, conn);
5212  list_insert_tail(&sys->net_send.conns_list, conn);
5213  }
5214 
5215  return (conn);
5216 }
5217 
5218 static void
5219 handle_net_req_map(elec_sys_t *sys, net_conn_t *conn, const net_req_map_t *req)
5220 {
5221  ASSERT(sys != NULL);
5222  ASSERT(conn != NULL);
5223  ASSERT(req != NULL);
5224 
5225  memcpy(conn->map, req->map, NETMAPSZ(sys));
5226  conn->num_active = 0;
5227  for (unsigned i = 0, n = list_count(&sys->comps); i < n; i++) {
5228  if (NETMAPGET(conn->map, i))
5229  conn->num_active++;
5230  }
5231  DELAY_LINE_PUSH_IMM(&conn->kill_delay, false);
5232 }
5233 
5234 static void
5235 netlink_send_msg_notif(netlink_conn_id_t conn_id, const void *buf, size_t sz,
5236  void *userinfo)
5237 {
5238  elec_sys_t *sys;
5239  const net_req_t *req;
5240  net_conn_t *conn;
5241 
5242  ASSERT(buf != NULL);
5243  ASSERT(userinfo != NULL);
5244  sys = userinfo;
5245 
5246  if (sz < sizeof (net_req_t)) {
5247  logMsg("Received bad packet length %d", (int)sz);
5248  return;
5249  }
5250  req = buf;
5251  if (req->version != LIBELEC_NET_VERSION) {
5252  logMsg("Received bad version %d which doesn't match ours (%d)",
5253  req->version, LIBELEC_NET_VERSION);
5254  return;
5255  }
5256  mutex_enter(&sys->worker_interlock);
5257  conn = get_net_conn(sys, conn_id);
5258  if (req->req == NET_REQ_MAP) {
5259  const net_req_map_t *map = buf;
5260 
5261  if (map->conf_crc == sys->conf_crc &&
5262  sz == sizeof (net_req_map_t) + NETMAPSZ(sys)) {
5263  handle_net_req_map(sys, conn, buf);
5264  } else {
5265 #if !IBM
5266  logMsg("Cannot handle net map req, elec file "
5267  "CRC mismatch (ours: %llx theirs: %llx)",
5268  (unsigned long long)sys->conf_crc,
5269  (unsigned long long)map->conf_crc);
5270 #else /* IBM */
5271  logMsg("Cannot handle net map req, elec file "
5272  "CRC mismatch");
5273 #endif /* IBM */
5274  }
5275  } else {
5276  logMsg("Unknown or malformed req %x of length %d",
5277  req->req, (int)sz);
5278  }
5279  mutex_exit(&sys->worker_interlock);
5280 }
5281 
5282 static void
5283 send_xmit_data_conn(elec_sys_t *sys, net_conn_t *conn)
5284 {
5285  size_t repsz;
5286  net_rep_comps_t *rep;
5287 
5288  ASSERT(sys != NULL);
5289  ASSERT_MUTEX_HELD(&sys->worker_interlock);
5290  ASSERT(conn != NULL);
5291 
5292  repsz = sizeof (net_rep_comps_t) +
5293  conn->num_active * sizeof (net_comp_data_t);
5294  rep = safe_calloc(1, repsz);
5295  rep->version = LIBELEC_NET_VERSION;
5296  rep->rep = NET_REP_COMPS;
5297  rep->conf_crc = sys->conf_crc;
5298  for (unsigned i = 0, n = list_count(&sys->comps); i < n; i++) {
5299  const elec_comp_t *comp = sys->comps_array[i];
5300 
5301  if (NETMAPGET(conn->map, i)) {
5302  net_comp_data_t *data = &rep->comps[rep->n_comps++];
5303  ASSERT3U(rep->n_comps, <=, conn->num_active);
5304 
5305  data->idx = i;
5306 
5307  if (comp->ro.failed)
5308  data->flags |= LIBELEC_NET_FLAG_FAILED;
5309  if (comp->ro.shorted)
5310  data->flags |= LIBELEC_NET_FLAG_SHORTED;
5311  data->in_volts = clampi(round(comp->ro.in_volts *
5312  NET_VOLTS_FACTOR), 0, UINT16_MAX);
5313  data->out_volts = clampi(round(comp->ro.out_volts *
5314  NET_VOLTS_FACTOR), 0, UINT16_MAX);
5315  data->in_amps = clampi(round(comp->ro.in_amps *
5316  NET_AMPS_FACTOR), 0, UINT16_MAX);
5317  data->out_amps = clampi(round(comp->ro.out_amps *
5318  NET_AMPS_FACTOR), 0, UINT16_MAX);
5319  data->in_freq = clampi(round(comp->ro.in_freq *
5320  NET_FREQ_FACTOR), 0, UINT16_MAX);
5321  data->out_freq = clampi(round(comp->ro.out_freq *
5322  NET_FREQ_FACTOR), 0, UINT16_MAX);
5323  data->leak_factor = round(comp->ro.leak_factor * 10000);
5324  }
5325  }
5326  ASSERT3U(rep->n_comps, ==, conn->num_active);
5327  (void)netlink_sendto(NETLINK_PROTO_LIBELEC, rep, repsz,
5328  conn->conn_id, 0);
5329 }
5330 
5331 static void
5332 elec_net_send_update(elec_sys_t *sys)
5333 {
5334  ASSERT(sys != NULL);
5335 
5336  sys->net_send.xmit_ctr = (sys->net_send.xmit_ctr + 1) % NET_XMIT_INTVAL;
5337  if (sys->net_send.xmit_ctr != 0)
5338  return;
5339  /*
5340  * Sending data can kill the conn and remove it from the list,
5341  * so be sure to grab the next conn pointer ahead of time.
5342  */
5343  mutex_enter(&sys->worker_interlock);
5344  for (net_conn_t *conn = list_head(&sys->net_send.conns_list),
5345  *next_conn = NULL; conn != NULL; conn = next_conn) {
5346  next_conn = list_next(&sys->net_send.conns_list, conn);
5347  send_xmit_data_conn(sys, conn);
5348  }
5349  mutex_exit(&sys->worker_interlock);
5350 }
5351 
5352 static void
5353 handle_net_rep_comps(elec_sys_t *sys, const net_rep_comps_t *comps)
5354 {
5355  ASSERT(sys != NULL);
5356  ASSERT(comps != NULL);
5357 
5358  NET_DBG_LOG("New dev data with %d comps", (int)comps->n_comps);
5359 
5360  for (unsigned i = 0; i < comps->n_comps; i++) {
5361  const net_comp_data_t *data = &comps->comps[i];
5362  elec_comp_t *comp;
5363 
5364  if (data->idx >= list_count(&sys->comps))
5365  continue;
5366  comp = sys->comps_array[data->idx];
5367 
5368  mutex_enter(&comp->rw_ro_lock);
5369 
5370  comp->rw.in_volts = (data->in_volts / NET_VOLTS_FACTOR);
5371  comp->rw.out_volts = (data->out_volts / NET_VOLTS_FACTOR);
5372  comp->rw.in_amps = (data->in_amps / NET_AMPS_FACTOR);
5373  comp->rw.out_amps = (data->out_amps / NET_AMPS_FACTOR);
5374  comp->rw.in_pwr = comp->rw.in_volts * comp->rw.in_amps;
5375  comp->rw.out_pwr = comp->rw.out_volts * comp->rw.out_amps;
5376  comp->rw.in_freq = (data->in_freq / NET_FREQ_FACTOR);
5377  comp->rw.out_freq = (data->out_freq / NET_FREQ_FACTOR);
5378  comp->rw.leak_factor = (data->leak_factor / 10000.0);
5379  comp->rw.failed = !!(data->flags & LIBELEC_NET_FLAG_FAILED);
5380  comp->rw.shorted = !!(data->flags & LIBELEC_NET_FLAG_SHORTED);
5381 
5382  comp->ro = comp->rw;
5383 
5384  mutex_exit(&comp->rw_ro_lock);
5385  }
5386 }
5387 
5388 static void
5389 netlink_recv_msg_notif(netlink_conn_id_t conn_id, const void *buf, size_t sz,
5390  void *userinfo)
5391 {
5392  elec_sys_t *sys;
5393  const net_rep_t *rep;
5394  const net_rep_comps_t *rep_comps;
5395 
5396  UNUSED(conn_id);
5397  ASSERT(buf != NULL);
5398  rep = buf;
5399  rep_comps = buf;
5400  ASSERT(userinfo != NULL);
5401  sys = userinfo;
5402 
5403  if (sz < sizeof (net_rep_t)) {
5404  logMsg("Received bad packet length %d", (int)sz);
5405  return;
5406  }
5407  if (rep->version != LIBELEC_NET_VERSION) {
5408  logMsg("Received bad version %d which doesn't "
5409  "match ours (%d)", rep->version, LIBELEC_NET_VERSION);
5410  return;
5411  }
5412  if (rep->rep == NET_REP_COMPS &&
5413  sz >= sizeof (net_rep_comps_t) &&
5414  sz == sizeof (net_rep_comps_t) + rep_comps->n_comps *
5415  sizeof (net_comp_data_t)) {
5416  if (rep_comps->conf_crc == sys->conf_crc) {
5417  handle_net_rep_comps(sys, rep_comps);
5418  } else {
5419 #if !IBM
5420  logMsg("Cannot handle rep COMPS, elec file "
5421  "CRC mismatch (ours: %llx theirs: %llx)",
5422  (unsigned long long)sys->conf_crc,
5423  (unsigned long long)rep_comps->conf_crc);
5424 #else /* IBM */
5425  logMsg("Cannot handle rep COMPS, elec file "
5426  "CRC mismatch");
5427 #endif /* IBM */
5428  }
5429  } else {
5430  logMsg("Unknown or malformed rep %x of length %d",
5431  rep->rep, (int)sz);
5432  }
5433 }
5434 
5435 static void
5436 elec_net_recv_update(elec_sys_t *sys)
5437 {
5438  ASSERT(sys != NULL);
5439  if (netlink_started() && sys->net_recv.map_dirty) {
5440  mutex_enter(&sys->worker_interlock);
5441  if (send_net_recv_map(sys))
5442  sys->net_recv.map_dirty = false;
5443  mutex_exit(&sys->worker_interlock);
5444  }
5445 }
5446 
5447 static bool
5448 send_net_recv_map(elec_sys_t *sys)
5449 {
5450  net_req_map_t *req;
5451  bool res;
5452 
5453  ASSERT(sys != NULL);
5454  ASSERT(sys->started);
5455  ASSERT(sys->net_recv.active);
5456  req = safe_calloc(1, NETMAPSZ_REQ(sys));
5457  req->version = LIBELEC_NET_VERSION;
5458  req->req = NET_REQ_MAP;
5459  req->conf_crc = sys->conf_crc;
5460  memcpy(req->map, sys->net_recv.map, NETMAPSZ(sys));
5461  res = netlink_send(NETLINK_PROTO_LIBELEC, req, NETMAPSZ_REQ(sys), 0);
5462  ZERO_FREE(req);
5463 
5464  return (res);
5465 }
5466 
5467 static void
5468 net_add_recv_comp(elec_comp_t *comp)
5469 {
5470  elec_sys_t *sys;
5471 
5472  ASSERT(comp != NULL);
5473  sys = comp->sys;
5474  if (!sys->net_recv.active)
5475  return;
5476  ASSERT3U(comp->comp_idx, <, list_count(&sys->comps));
5477  if (!NETMAPGET(sys->net_recv.map, comp->comp_idx)) {
5478  mutex_enter(&sys->worker_interlock);
5479  if (!NETMAPGET(sys->net_recv.map, comp->comp_idx)) {
5480  NETMAPSET(sys->net_recv.map, comp->comp_idx);
5481  sys->net_recv.map_dirty = true;
5482  }
5483  mutex_exit(&sys->worker_interlock);
5484  }
5485 }
5486 
5487 #endif /* defined(LIBELEC_WITH_NETLINK) */
double libelec_batt_get_chg_rel(const elec_comp_t *batt)
Definition: libelec.c:4938
bool libelec_comp_is_AC(const elec_comp_t *comp)
Definition: libelec.c:2590
double libelec_gen_set_random_freq(elec_comp_t *comp, double stddev)
Definition: libelec.c:2864
void libelec_comp_set_userinfo(elec_comp_t *comp, void *userinfo)
Definition: libelec.c:2882
double libelec_comp_get_out_amps(const elec_comp_t *comp)
Definition: libelec.c:2360
void libelec_gen_set_rpm_cb(elec_comp_t *gen, elec_get_rpm_cb_t cb)
Definition: libelec.c:2956
void libelec_sys_set_time_factor(elec_sys_t *sys, double time_factor)
Definition: libelec.c:1044
elec_get_rpm_cb_t libelec_gen_get_rpm_cb(const elec_comp_t *gen)
Definition: libelec.c:2970
double(* elec_get_load_cb_t)(elec_comp_t *comp, void *userinfo)
Definition: libelec.h:316
elec_comp_t * libelec_comp_find(elec_sys_t *sys, const char *name)
Definition: libelec.c:2147
elec_get_load_cb_t libelec_load_get_load_cb(elec_comp_t *load)
Definition: libelec.c:3007
double libelec_comp_get_out_freq(const elec_comp_t *comp)
Definition: libelec.c:2546
void libelec_tie_set(elec_comp_t *comp,...) SENTINEL_ATTR
Definition: libelec.c:4659
double libelec_phys_get_batt_voltage(double U_nominal, double chg_rel, double I_rel)
Definition: libelec.c:5033
double(* elec_get_temp_cb_t)(elec_comp_t *comp, void *userinfo)
Definition: libelec.h:216
double libelec_comp_get_eff(const elec_comp_t *gen)
Definition: libelec.c:2682
void libelec_walk_comps(const elec_sys_t *sys, void(*cb)(elec_comp_t *, void *), void *userinfo)
Definition: libelec.c:2108
const elec_comp_info_t * libelec_get_comp_infos(const elec_sys_t *sys, size_t *num_infos)
Definition: libelec.c:539
bool libelec_cb_get(const elec_comp_t *comp)
Definition: libelec.c:4569
void libelec_sys_stop(elec_sys_t *sys)
Definition: libelec.c:1003
bool libelec_comp_get_failed(const elec_comp_t *comp)
Definition: libelec.c:2755
double libelec_comp_get_out_pwr(const elec_comp_t *comp)
Definition: libelec.c:2469
const elec_comp_info_t * libelec_comp2info(const elec_comp_t *comp)
Definition: libelec.c:2131
bool libelec_comp_get_shorted(const elec_comp_t *comp)
Definition: libelec.c:2789
double libelec_batt_get_temp(const elec_comp_t *batt)
Definition: libelec.c:4974
void libelec_batt_set_temp_cb(elec_comp_t *batt, elec_get_temp_cb_t cb)
Definition: libelec.c:2918
double libelec_comp_get_in_freq(const elec_comp_t *comp)
Definition: libelec.c:2504
bool libelec_deserialize(elec_sys_t *sys, const conf_t *ser, const char *prefix)
Definition: libelec.c:1245
void libelec_load_set_load_cb(elec_comp_t *load, elec_get_load_cb_t cb)
Definition: libelec.c:2993
bool libelec_comp_is_powered(const elec_comp_t *comp)
Definition: libelec.c:2670
elec_sys_t * libelec_new(const char *filename)
Definition: libelec.c:737
double libelec_comp_get_in_volts(const elec_comp_t *comp)
Definition: libelec.c:2232
bool libelec_tie_get(elec_comp_t *tie, bool_t exhaustive,...) SENTINEL_ATTR
Definition: libelec.c:4825
double libelec_comp_get_in_pwr(const elec_comp_t *comp)
Definition: libelec.c:2410
void libelec_batt_set_temp(elec_comp_t *batt, double T)
Definition: libelec.c:4994
size_t libelec_tie_get_num_buses(const elec_comp_t *comp)
Definition: libelec.c:4799
void libelec_comp_set_shorted(elec_comp_t *comp, bool shorted)
Definition: libelec.c:2775
void libelec_add_user_cb(elec_sys_t *sys, bool pre, elec_user_cb_t cb, void *userinfo)
Definition: libelec.c:2048
void * libelec_comp_get_userinfo(const elec_comp_t *comp)
Definition: libelec.c:2895
elec_comp_type_t
Definition: libelec.h:51
@ ELEC_SHUNT
Definition: libelec.h:163
@ ELEC_TIE
Definition: libelec.h:194
@ ELEC_LABEL_BOX
Definition: libelec.h:207
@ ELEC_DIODE
Definition: libelec.h:201
@ ELEC_LOAD
Definition: libelec.h:113
@ ELEC_BUS
Definition: libelec.h:121
@ ELEC_GEN
Definition: libelec.h:73
@ ELEC_TRU
Definition: libelec.h:89
@ ELEC_INV
Definition: libelec.h:96
@ ELEC_CB
Definition: libelec.h:147
@ ELEC_BATT
Definition: libelec.h:64
@ ELEC_XFRMR
Definition: libelec.h:103
double libelec_gen_get_rpm(const elec_comp_t *gen)
Definition: libelec.c:4920
void libelec_tie_set_list(elec_comp_t *comp, size_t list_len, elec_comp_t *const bus_list[])
Definition: libelec.c:4605
double libelec_comp_get_in_amps(const elec_comp_t *comp)
Definition: libelec.c:2323
bool libelec_sys_is_started(const elec_sys_t *sys)
Definition: libelec.c:936
void libelec_tie_set_all(elec_comp_t *comp, bool tied)
Definition: libelec.c:4713
double libelec_cb_get_temp(const elec_comp_t *comp)
Definition: libelec.c:4584
void libelec_batt_set_chg_rel(elec_comp_t *batt, double chg_rel)
Definition: libelec.c:4953
double libelec_gen_set_random_volts(elec_comp_t *comp, double stddev)
Definition: libelec.c:2849
bool libelec_chgr_get_working(const elec_comp_t *chgr)
Definition: libelec.c:5009
size_t libelec_tie_get_list(elec_comp_t *comp, size_t cap, elec_comp_t *bus_list[])
Definition: libelec.c:4768
elec_comp_t * libelec_comp_get_conn(const elec_comp_t *comp, size_t i)
Definition: libelec.c:2194
void(* elec_user_cb_t)(elec_sys_t *sys, bool pre, void *userinfo)
Definition: libelec.h:445
double libelec_comp_get_out_volts(const elec_comp_t *comp)
Definition: libelec.c:2276
double(* elec_get_rpm_cb_t)(elec_comp_t *comp, void *userinfo)
Definition: libelec.h:249
double libelec_comp_get_incap_volts(const elec_comp_t *comp)
Definition: libelec.c:2574
void libelec_serialize(elec_sys_t *sys, conf_t *ser, const char *prefix)
Definition: libelec.c:1205
size_t libelec_comp_get_num_conns(const elec_comp_t *comp)
Definition: libelec.c:2164
void libelec_tie_set_v(elec_comp_t *comp, va_list ap)
Definition: libelec.c:4676
elec_get_temp_cb_t libelec_batt_get_temp_cb(const elec_comp_t *batt)
Definition: libelec.c:2933
void libelec_comp_set_failed(elec_comp_t *comp, bool failed)
Definition: libelec.c:2742
void libelec_destroy(elec_sys_t *sys)
Definition: libelec.c:1287
void libelec_gen_set_rpm(elec_comp_t *gen, double rpm)
Definition: libelec.c:4893
bool libelec_sys_start(elec_sys_t *sys)
Definition: libelec.c:977
bool libelec_sys_can_start(const elec_sys_t *sys)
Definition: libelec.c:951
double libelec_sys_get_time_factor(const elec_sys_t *sys)
Definition: libelec.c:1081
bool libelec_tie_get_all(elec_comp_t *comp)
Definition: libelec.c:4738
void libelec_cb_set(elec_comp_t *comp, bool set)
Definition: libelec.c:4551
void libelec_remove_user_cb(elec_sys_t *sys, bool pre, elec_user_cb_t cb, void *userinfo)
Definition: libelec.c:2076
bool libelec_tie_get_v(elec_comp_t *tie, bool exhaustive, va_list ap)
Definition: libelec.c:4844