14 #include <XPLMDisplay.h>
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>
25 #ifdef LIBELEC_WITH_DRS
26 #include <acfutils/dr.h>
29 #ifdef LIBELEC_WITH_LIBSWITCH
30 #include <libswitch.h>
33 #ifdef LIBELEC_WITH_NETLINK
38 #include "libelec_types_impl.h"
40 #define EXEC_INTVAL 40000
41 #define MAX_NETWORK_DEPTH 100
42 #define NO_NEG_ZERO(x) ((x) == 0.0 ? 0.0 : (x))
43 #define CB_SW_ON_DELAY 0.33
44 #define MAX_COMPS (UINT16_MAX + 1)
45 #define GEN_MIN_RPM 1e-3
47 #ifdef LIBELEC_WITH_NETLINK
49 #define LIBELEC_NET_VERSION 1
50 #define NETMAPGET(map, idx) \
51 ((((map)[(idx) >> 3]) & (1 << ((idx) & 7))) != 0)
52 #define NETMAPSET(map, idx) \
54 (map)[(idx) >> 3] |= (1 << ((idx) & 7)); \
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);
62 #define NET_XMIT_INTVAL 5
63 #define NET_VOLTS_FACTOR 20.0
64 #define NET_AMPS_FACTOR 40.0
65 #define NET_FREQ_FACTOR 20.0
67 #define NET_ADD_RECV_COMP(comp) net_add_recv_comp((elec_comp_t *)comp)
71 #ifdef LIBELEC_NET_DBG
72 #define NET_DBG_LOG(...) logMsg(__VA_ARGS__)
74 #define NET_DBG_LOG(...)
79 #define NET_ADD_RECV_COMP(comp)
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},
100 {C2KELVIN(15), 0.925},
105 static elec_comp_info_t *infos_parse(
const char *filename,
size_t *num_infos);
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);
117 static double network_trace(
const elec_comp_t *upstream,
118 const elec_comp_t *comp,
unsigned depth,
bool do_print);
120 #ifdef LIBELEC_WITH_NETLINK
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);
127 static void elec_net_send_update(elec_sys_t *sys);
128 static void elec_net_recv_update(elec_sys_t *sys);
140 elec_draw_cb(XPLMDrawingPhase phase,
int before,
void *refcon)
143 double sim_time, time_factor;
147 ASSERT(refcon != NULL);
150 sim_time = dr_getf_prot(&sys->drs.sim_time);
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) {
157 worker_set_interval_nowake(&sys->worker, EXEC_INTVAL);
158 mutex_enter(&sys->paused_lock);
160 sys->time_factor = 0;
161 mutex_exit(&sys->paused_lock);
164 sys->prev_sim_time = sim_time;
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);
175 mutex_enter(&sys->paused_lock);
177 sys->time_factor = time_factor;
178 mutex_exit(&sys->paused_lock);
187 ASSERT(info != NULL);
188 switch (info->type) {
190 return (info->gen.freq != 0);
201 check_upstream(
const elec_comp_t *comp,
const elec_comp_t *src,
202 const elec_comp_t *upstream)
204 ASSERT(comp != NULL);
206 ASSERT3U(src->src_idx, <, ELEC_MAX_SRCS);
207 ASSERT(upstream != NULL);
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);
217 get_src_fract(
const elec_comp_t *comp,
const elec_comp_t *src)
219 ASSERT(comp != NULL);
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));
235 ASSERT(link != NULL);
236 for (
unsigned i = 0; i < ELEC_MAX_SRCS; i++)
237 amps += link->out_amps[i];
243 user_cb_info_compar(
const void *a,
const void *b)
246 const uintptr_t cb_a = (uintptr_t)ucbi_a->cb;
247 const uintptr_t cb_b = (uintptr_t)ucbi_a->cb;
249 if (!ucbi_a->pre && ucbi_b->pre)
251 if (ucbi_a->pre && !ucbi_b->pre)
257 if (ucbi_a->userinfo < ucbi_b->userinfo)
259 if (ucbi_a->userinfo > ucbi_b->userinfo)
265 name2comp_compar(
const void *a,
const void *b)
267 const elec_comp_t *ca = a, *cb = b;
268 int res = strcmp(ca->info->name, cb->info->name);
277 info2comp_compar(
const void *a,
const void *b)
279 const elec_comp_t *ca = a, *cb = b;
280 if (ca->info < cb->info)
282 if (ca->info > cb->info)
288 find_comp(elec_sys_t *sys,
const elec_comp_info_t *info,
const elec_comp_t *src)
294 ASSERT(info != NULL);
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);
305 resolve_bus_links(elec_sys_t *sys, elec_comp_t *bus)
309 ASSERT(bus->info != NULL);
310 ASSERT3U(bus->info->type, ==,
ELEC_BUS);
312 bus->n_links = bus->info->bus.n_comps;
313 bus->links = safe_calloc(bus->n_links, sizeof (*bus->links));
315 #define CHECK_COMP(cond, reason) \
318 logMsg("%s (%s:%d): %s", comp->info->name, \
319 sys->conf_filename, comp->info->parse_linenum, \
324 #define CHECK_COMP_V(cond, reason, ...) \
327 logMsg("%s (%s:%d): " reason, comp->info->name, \
328 sys->conf_filename, comp->info->parse_linenum, \
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],
338 bus->links[i].comp = comp;
339 ASSERT(comp->info != NULL);
340 switch (comp->info->type) {
343 CHECK_COMP(!bus->info->bus.ac,
"batteries cannot "
344 "connect to AC buses (batteries are inherently "
346 ASSERT3U(comp->n_links, ==, 1);
347 ASSERT(comp->links != NULL);
348 comp->links[0].comp = bus;
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;
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;
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;
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;
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;
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;
400 ASSERT3P(comp->info->xfrmr.output, ==,
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;
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;
418 CHECK_COMP_V(
false,
"Invalid link: cannot connect "
419 "bus %s directly to bus %s", bus->info->name,
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");
430 ASSERT3U(comp->n_links, ==, 2);
431 ASSERT(comp->links != NULL);
432 if (comp->links[0].comp == NULL) {
433 comp->links[0].comp = bus;
435 elec_comp_t *other_bus = comp->links[0].comp;
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 "
445 bus->info->bus.ac ?
"AC" :
"DC",
446 other_bus->info->name,
447 other_bus->info->bus.ac ?
"AC" :
"DC");
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));
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;
476 ASSERT3P(comp->info->diode.sides[1], ==,
478 comp->links[1].comp = bus;
491 resolve_comp_links(elec_sys_t *sys)
495 for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
496 comp = list_next(&sys->comps, comp)) {
498 if (!resolve_bus_links(sys, comp))
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);
511 WARN_UNUSED_RES_ATTR
static bool
512 check_comp_links(elec_sys_t *sys)
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",
542 ASSERT(num_infos != NULL);
543 *num_infos = sys->num_infos;
544 return (sys->comp_infos);
550 elec_comp_t *comp = safe_calloc(1,
sizeof (*comp));
552 ASSERT(info != NULL);
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);
562 VERIFY_MSG(avl_find(&sys->name2comp, comp, &where) ==
563 NULL,
"Duplicate info name %s", info->name);
564 avl_insert(&sys->name2comp, comp, where);
568 switch (comp->info->type) {
570 comp->load.random_load_factor = 1;
573 comp->src_idx = *src_i;
575 mutex_init(&comp->batt.lock);
576 comp->batt.chg_rel = 1.0;
577 comp->batt.T = C2KELVIN(15);
580 comp->src_idx = *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;
602 comp->src_idx = *src_i;
606 comp->src_idx = *src_i;
614 comp->scb.cur_set = comp->scb.wk_set =
true;
617 mutex_init(&comp->tie.lock);
627 switch (comp->info->type) {
646 if (comp->n_links != 0)
647 comp->links = safe_calloc(comp->n_links, sizeof (*comp->links));
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);
661 list_insert_tail(&sys->gens_batts, comp);
662 else if (comp->info->type ==
ELEC_TIE)
663 list_insert_tail(&sys->ties, comp);
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);
739 elec_sys_t *sys = safe_calloc(1,
sizeof (*sys));
740 unsigned src_i = 0, comp_i = 0;
744 ASSERT(filename != NULL);
746 buf = file2buf(filename, &bufsz);
748 logMsg(
"Can't open %s: %s", filename, strerror(errno));
752 sys->conf_filename = safe_strdup(filename);
753 sys->conf_crc = crc64(buf, bufsz);
756 sys->comp_infos = infos_parse(filename, &sys->num_infos);
757 if (sys->comp_infos == NULL) {
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));
772 mutex_init(&sys->user_cbs_lock);
773 avl_create(&sys->user_cbs, user_cb_info_compar,
775 mutex_init(&sys->worker_interlock);
776 mutex_init(&sys->paused_lock);
777 sys->time_factor = 1;
779 for (
size_t i = 0; i < sys->num_infos; i++) {
780 if (!comp_alloc(sys, &sys->comp_infos[i], &src_i))
784 if (!resolve_comp_links(sys) || !check_comp_links(sys))
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;
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,
822 validate_elec_comp_infos_parse(
const elec_comp_info_t *infos,
size_t n_infos,
823 const char *filename)
825 ASSERT(infos != NULL || n_infos == 0);
826 ASSERT(filename != NULL);
828 #define CHECK_COMP(cond, reason) \
831 logMsg("%s (%s:%d): %s", info->name, filename, \
832 info->parse_linenum, (reason)); \
837 for (
size_t i = 0; i < n_infos; i++) {
840 switch (info->type) {
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");
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");
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");
873 CHECK_COMP(info->tru.out_freq > 0,
874 "missing required \"OUT_FREQ\" parameter");
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");
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");
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);
906 CHECK_COMP(info->bus.comps != NULL,
907 "buses must connect to at least 1 component");
914 CHECK_COMP(info->diode.sides[0] != NULL &&
915 info->diode.sides[1] != NULL,
916 "diodes need to have both end points connected");
939 return (sys->started);
954 return (!sys->started);
984 #ifndef LIBELEC_SLOW_DEBUG
985 worker_init(&sys->worker, elec_sys_worker, EXEC_INTVAL, sys,
988 worker_init(&sys->worker, elec_sys_worker, 0, sys,
"elec_sys");
1005 ASSERT(sys != NULL);
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);
1017 sys->started =
false;
1046 ASSERT(sys != NULL);
1047 ASSERT3F(time_factor, >=, 0);
1049 if (time_factor == 0) {
1052 worker_set_interval_nowake(&sys->worker, EXEC_INTVAL);
1053 mutex_enter(&sys->paused_lock);
1055 sys->time_factor = 0;
1056 mutex_exit(&sys->paused_lock);
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);
1069 mutex_enter(&sys->paused_lock);
1070 sys->paused =
false;
1071 sys->time_factor = time_factor;
1072 mutex_exit(&sys->paused_lock);
1083 ASSERT(sys != NULL);
1084 return (sys->time_factor);
1088 elec_comp_serialize(elec_comp_t *comp, conf_t *ser,
const char *prefix)
1090 ASSERT(comp != NULL);
1091 ASSERT(comp->info != NULL);
1092 ASSERT(comp->info->name != NULL);
1093 ASSERT(ser != NULL);
1094 ASSERT(prefix != NULL);
1096 LIBELEC_SERIALIZE_DATA_V(comp, ser,
"%s/%s/data",
1097 prefix, comp->info->name);
1099 switch (comp->info->type) {
1101 LIBELEC_SERIALIZE_DATA_V(&comp->batt, ser,
"%s/%s/batt",
1102 prefix, comp->info->name);
1105 LIBELEC_SERIALIZE_DATA_V(&comp->batt, ser,
"%s/%s/gen",
1106 prefix, comp->info->name);
1109 LIBELEC_SERIALIZE_DATA_V(&comp->batt, ser,
"%s/%s/load",
1110 prefix, comp->info->name);
1113 LIBELEC_SERIALIZE_DATA_V(&comp->scb, ser,
"%s/%s/cb",
1114 prefix, comp->info->name);
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);
1132 elec_comp_deserialize(elec_comp_t *comp,
const conf_t *ser,
const char *prefix)
1134 ASSERT(comp != NULL);
1135 ASSERT(comp->info != NULL);
1136 ASSERT(comp->info->name != NULL);
1137 ASSERT(ser != NULL);
1138 ASSERT(prefix != NULL);
1140 LIBELEC_DESERIALIZE_DATA_V(comp, ser,
"%s/%s/data",
1141 prefix, comp->info->name);
1143 switch (comp->info->type) {
1145 LIBELEC_DESERIALIZE_DATA_V(&comp->batt, ser,
"%s/%s/batt",
1146 prefix, comp->info->name);
1149 LIBELEC_DESERIALIZE_DATA_V(&comp->batt, ser,
"%s/%s/gen",
1150 prefix, comp->info->name);
1153 LIBELEC_DESERIALIZE_DATA_V(&comp->batt, ser,
"%s/%s/load",
1154 prefix, comp->info->name);
1157 LIBELEC_DESERIALIZE_DATA_V(&comp->scb, ser,
"%s/%s/cb",
1158 prefix, comp->info->name);
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);
1172 mutex_exit(&comp->tie.lock);
1207 ASSERT(sys != NULL);
1208 ASSERT(ser != NULL);
1209 ASSERT(prefix != NULL);
1210 #ifdef LIBELEC_WITH_NETLINK
1211 ASSERT(!sys->net_recv.active);
1213 conf_set_data_v(ser,
"%s/conf_crc64", &sys->conf_crc,
1214 sizeof (sys->conf_crc), prefix);
1216 mutex_enter(&sys->worker_interlock);
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);
1223 mutex_exit(&sys->worker_interlock);
1249 ASSERT(sys != NULL);
1250 ASSERT(ser != NULL);
1251 ASSERT(prefix != NULL);
1252 #ifdef LIBELEC_WITH_NETLINK
1253 ASSERT(!sys->net_recv.active);
1255 if (!conf_get_data_v(ser,
"%s/conf_crc64", &crc,
sizeof (crc),
1257 logMsg(
"Cannot deserialize libelec state: missing required "
1258 "state key %s/conf_crc64", prefix);
1261 if (crc != sys->conf_crc) {
1262 logMsg(
"Cannot deserialize libelec state: configuration "
1263 "file CRC mismatch");
1266 mutex_enter(&sys->worker_interlock);
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",
1276 mutex_exit(&sys->worker_interlock);
1293 ASSERT(sys != NULL);
1296 ASSERT(!sys->started);
1298 #ifdef LIBELEC_WITH_NETLINK
1299 libelec_disable_net_send(sys);
1300 libelec_disable_net_recv(sys);
1304 while ((ucbi = avl_destroy_nodes(&sys->user_cbs, &cookie)) != NULL)
1306 avl_destroy(&sys->user_cbs);
1307 mutex_destroy(&sys->user_cbs_lock);
1310 while (avl_destroy_nodes(&sys->info2comp, &cookie) != NULL)
1312 avl_destroy(&sys->info2comp);
1315 while (avl_destroy_nodes(&sys->name2comp, &cookie) != NULL)
1317 avl_destroy(&sys->info2comp);
1319 while (list_remove_head(&sys->gens_batts) != NULL)
1321 list_destroy(&sys->gens_batts);
1323 while (list_remove_head(&sys->ties) != NULL)
1325 list_destroy(&sys->ties);
1327 while ((comp = list_remove_head(&sys->comps)) != NULL)
1329 list_destroy(&sys->comps);
1330 free(sys->comps_array);
1332 mutex_destroy(&sys->worker_interlock);
1333 mutex_destroy(&sys->paused_lock);
1335 infos_free(sys->comp_infos, sys->num_infos);
1337 free(sys->conf_filename);
1339 (void)XPLMUnregisterDrawCallback(elec_draw_cb,
1340 xplm_Phase_Window, 0, sys);
1343 memset(sys, 0,
sizeof (*sys));
1347 #ifdef LIBELEC_WITH_LIBSWITCH
1350 libelec_create_cb_switches(
const elec_sys_t *sys,
const char *prefix,
1353 ASSERT(sys != NULL);
1354 ASSERT(prefix != NULL);
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];
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), <,
1367 comp->scb.sw = libswitch_add_toggle(name, desc,
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,
1380 #ifdef LIBELEC_SLOW_DEBUG
1383 libelec_step(elec_sys_t *sys)
1385 ASSERT(sys != NULL);
1386 worker_wake_up(&sys->worker);
1424 const char *slot_qual)
1426 switch (info->type) {
1429 if (slot_qual == NULL)
1431 if (strcmp(slot_qual,
"AC") == 0) {
1432 if (info->tru.ac != NULL)
1434 info->tru.ac = info2;
1435 }
else if (strcmp(slot_qual,
"DC") == 0) {
1436 if (info->tru.dc != NULL)
1438 info->tru.dc = info2;
1444 if (slot_qual == NULL)
1446 if (strcmp(slot_qual,
"IN") == 0) {
1447 if (info->xfrmr.input != NULL) {
1450 info->xfrmr.input = info2;
1451 }
else if (strcmp(slot_qual,
"OUT") == 0) {
1452 if (info->xfrmr.output != NULL) {
1455 info->xfrmr.output = info2;
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++;
1467 if (slot_qual == NULL)
1469 if (strcmp(slot_qual,
"IN") == 0) {
1470 if (info->diode.sides[0] != NULL)
1472 info->diode.sides[0] = info2;
1474 if (strcmp(slot_qual,
"OUT") != 0)
1476 if (info->diode.sides[1] != NULL)
1478 info->diode.sides[1] = info2;
1489 find_comp_info(
elec_comp_info_t *infos,
size_t num_comps,
const char *name)
1491 for (
unsigned i = 0; i < num_comps; i++) {
1492 if (infos[i].name != NULL && strcmp(infos[i].name, name) == 0)
1498 static gui_load_type_t
1499 str2load_type(
const char *str)
1501 ASSERT(str != NULL);
1502 if (strcmp(str,
"MOTOR") == 0)
1503 return (GUI_LOAD_MOTOR);
1504 return (GUI_LOAD_GENERIC);
1508 infos_parse(
const char *filename,
size_t *num_infos)
1510 #define MAX_BUS_UNIQ 256
1511 uint64_t bus_IDs_seen[256] = { 0 };
1512 unsigned bus_ID_cur = 0;
1514 size_t comp_i = 0, num_comps = 0;
1519 unsigned linenum = 0;
1521 ASSERT(filename != NULL);
1522 ASSERT(num_infos != NULL);
1524 fp = fopen(filename,
"r");
1526 logMsg(
"Can't open electrical network file %s: %s",
1527 filename, strerror(errno));
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) {
1547 }
else if (strncmp(line,
"LOADCB ", 7) == 0 ||
1548 strncmp(line,
"LOADCB3 ", 8) == 0) {
1554 infos = safe_calloc(num_comps,
sizeof (*infos));
1555 for (
size_t i = 0; i < num_comps; i++) {
1557 info->gui.pos = NULL_VECT2;
1561 while (parser_get_next_line(fp, &line, &linecap, &linenum) > 0) {
1566 #define INVALID_LINE_FOR_COMP_TYPE \
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); \
1573 #define CHECK_DUP_NAME(__name__) \
1575 elec_comp_info_t *info2 = find_comp_info(infos, comp_i, \
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); \
1586 #define CHECK_COMP(cond, reason) \
1589 logMsg("%s:%d: %s", filename, linenum, (reason)); \
1590 free_strlist(comps, n_comps); \
1594 #define CHECK_COMP_V(cond, reason, ...) \
1597 logMsg("%s:%d: " reason, filename, linenum, \
1599 free_strlist(comps, n_comps); \
1604 comps = strsplit(line,
" ",
true, &n_comps);
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;
1612 info->name = safe_strdup(comps[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;
1620 info->name = safe_strdup(comps[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;
1628 info->name = safe_strdup(comps[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;
1636 info->name = safe_strdup(comps[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;
1644 info->name = safe_strdup(comps[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;
1653 info->name = safe_strdup(comps[1]);
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;
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));
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;
1673 info->name = safe_strdup(comps[1]);
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;
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;
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;
1697 info->name = safe_strdup(comps[1]);
1698 }
else if (strcmp(cmd,
"LABEL_BOX") == 0 && n_comps >= 7) {
1700 ASSERT3U(comp_i, <, num_comps);
1701 info = &infos[comp_i++];
1702 info->parse_linenum = linenum;
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 ?
" " :
"");
1713 }
else if (strcmp(cmd,
"VOLTS") == 0 && n_comps == 2 &&
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");
1724 INVALID_LINE_FOR_COMP_TYPE;
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 ||
1739 info->tru.in_volts = atof(comps[1]);
1740 }
else if (strcmp(cmd,
"IN_VOLTS") == 0 && n_comps == 2 &&
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 ||
1746 info->tru.out_volts = atof(comps[1]);
1747 }
else if (strcmp(cmd,
"OUT_VOLTS") == 0 && n_comps == 2 &&
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 ||
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 "
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)",
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]);
1772 info->load.incap_leak_Qps = atof(comps[3]);
1774 info->load.incap_leak_Qps =
1775 info->load.incap_C / 200;
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 &&
1823 switch (info->type) {
1825 curve_pp = &info->gen.eff_curve;
1829 curve_pp = &info->tru.eff_curve;
1832 curve_pp = &info->xfrmr.eff_curve;
1835 INVALID_LINE_FOR_COMP_TYPE;
1838 if (*curve_pp != NULL) {
1839 for (vect2_t *curve = *curve_pp;
1840 !IS_NULL_VECT(*curve); curve++)
1845 *curve_pp = safe_realloc(*curve_pp,
1846 (num + 2) *
sizeof (vect2_t));
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]));
1854 find_comp_info(infos, num_comps, comps[1]);
1856 if (info2 == NULL) {
1857 logMsg(
"%s:%d: unknown component %s",
1858 filename, linenum, comps[1]);
1859 free_strlist(comps, n_comps);
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);
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",
1878 free_strlist(comps, n_comps);
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) {
1886 ASSERT3U(comp_i + 1, <, num_comps);
1887 cb = &infos[comp_i++];
1888 cb->parse_linenum = linenum;
1890 cb->name = sprintf_alloc(
"CB_%s", info->name);
1892 cb->cb.max_amps = atof(comps[1]);
1893 cb->cb.triphase = (strcmp(cmd,
"LOADCB3") == 0);
1896 strlcpy(cb->location, comps[2],
1897 sizeof (cb->location));
1900 bus = &infos[comp_i++];
1901 bus->parse_linenum = linenum;
1903 bus->name = sprintf_alloc(
"CB_BUS_%s", info->name);
1904 bus->bus.ac = info->load.ac;
1905 bus->autogen =
true;
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",
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]));
1922 info->gui.sz = atof(comps[3]);
1925 }
else if (strcmp(cmd,
"GUI_ROT") == 0 && n_comps == 2 &&
1927 info->gui.rot = atof(comps[1]);
1928 }
else if (strcmp(cmd,
"GUI_LOAD") == 0 && n_comps == 2 &&
1930 info->gui.load_type = str2load_type(comps[1]);
1931 }
else if (strcmp(cmd,
"GUI_VIRT") == 0 && n_comps == 1 &&
1933 info->gui.virt =
true;
1934 }
else if (strcmp(cmd,
"GUI_INVIS") == 0 && n_comps == 1 &&
1936 info->gui.invis =
true;
1937 }
else if (strcmp(cmd,
"GUI_COLOR") == 0 && n_comps == 4 &&
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;
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);
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);
1960 info->tru.curr_lim = atof(comps[3]);
1961 if (info->tru.curr_lim <= 0) {
1962 logMsg(
"%s:%d: current limit must be positive",
1964 free_strlist(comps, n_comps);
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 ||
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 &&
1981 strlcpy(info->location, comps[1],
1982 sizeof (info->location));
1984 logMsg(
"%s:%d: unknown or malformed line",
1986 free_strlist(comps, n_comps);
1989 free_strlist(comps, n_comps);
1992 if (!validate_elec_comp_infos_parse(infos, num_comps, filename))
1995 #undef INVALID_LINE_FOR_COMP_TYPE
1996 #undef CHECK_DUP_NAME
2002 *num_infos = num_comps;
2016 ASSERT(infos != NULL || num_infos == 0);
2017 for (
size_t i = 0; i < num_infos; i++) {
2022 free(info->gen.eff_curve);
2024 free(info->tru.eff_curve);
2026 free(info->xfrmr.eff_curve);
2028 ZERO_FREE_N(info->bus.comps, info->bus.n_comps);
2030 ZERO_FREE_N(infos, num_infos);
2054 ASSERT(sys != NULL);
2059 info->userinfo = userinfo;
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);
2081 ASSERT(sys != NULL);
2086 srch.userinfo = userinfo;
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);
2111 ASSERT(sys != NULL);
2119 for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
2120 comp = list_next(&sys->comps, comp)) {
2133 ASSERT(comp != NULL);
2135 return (comp->info);
2150 const elec_comp_t srch_comp = { .info = &srch_info };
2152 ASSERT(sys != NULL);
2154 return (avl_find(&sys->name2comp, &srch_comp, NULL));
2166 ASSERT(comp != NULL);
2167 ASSERT(comp->info != NULL);
2170 switch (comp->info->type) {
2172 return (comp->n_links);
2174 return (comp->n_links);
2196 ASSERT(comp != NULL);
2197 ASSERT(comp->info != NULL);
2198 ASSERT3U(i, <, comp->n_links);
2200 return (comp->links[i].comp);
2236 ASSERT(comp != NULL);
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);
2280 ASSERT(comp != NULL);
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);
2327 ASSERT(comp != NULL);
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);
2364 ASSERT(comp != NULL);
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);
2414 ASSERT(comp != NULL);
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);
2473 ASSERT(comp != NULL);
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);
2508 ASSERT(comp != NULL);
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);
2550 ASSERT(comp != NULL);
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);
2576 ASSERT(comp != NULL);
2577 ASSERT(comp->info != NULL);
2578 ASSERT3U(comp->info->type, ==,
ELEC_LOAD);
2579 return (comp->load.incap_U);
2592 ASSERT(comp != NULL);
2593 ASSERT(comp->info != NULL);
2594 switch (comp->info->type) {
2603 return (comp->info->gen.freq != 0);
2605 ASSERT(comp->links[0].comp != NULL);
2606 return (comp->links[0].comp->info->bus.ac);
2608 return (comp->info->bus.ac);
2611 ASSERT(comp->links[0].comp != 0);
2612 return (comp->links[0].comp->info->bus.ac);
2615 ASSERT(comp->n_links != 0);
2616 ASSERT(comp->links[0].comp != NULL);
2617 return (comp->links[0].comp->info->bus.ac);
2625 libelec_comp_get_type(
const elec_comp_t *comp)
2627 ASSERT(comp != NULL);
2628 return (comp->info->type);
2632 libelec_comp_get_name(
const elec_comp_t *comp)
2634 ASSERT(comp != NULL);
2635 return (comp->info->name);
2639 libelec_comp_get_location(
const elec_comp_t *comp)
2641 ASSERT(comp != NULL);
2642 return (comp->info->location);
2646 libelec_comp_get_autogen(
const elec_comp_t *comp)
2648 ASSERT(comp != NULL);
2649 return (comp->info->autogen);
2672 ASSERT(comp != NULL);
2685 ASSERT(comp != NULL);
2686 switch (comp->info->type) {
2688 mutex_enter(&((elec_comp_t *)comp)->gen.lock);
2689 eff = comp->gen.eff;
2690 mutex_exit(&((elec_comp_t *)comp)->gen.lock);
2694 eff = comp->tru.eff;
2697 eff = comp->xfrmr.eff;
2706 libelec_comp_get_srcs(
const elec_comp_t *comp,
2707 elec_comp_t *srcs[CONST_ARRAY_LEN_ARG(ELEC_MAX_SRCS)])
2709 ASSERT(comp != NULL);
2710 ASSERT(srcs != NULL);
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);
2716 for (
unsigned i = 0; i < ELEC_MAX_SRCS; i++) {
2717 if (srcs[i] == NULL)
2720 return (ELEC_MAX_SRCS);
2744 ASSERT(comp != NULL);
2745 mutex_enter(&comp->rw_ro_lock);
2746 comp->ro.failed = failed;
2747 mutex_exit(&comp->rw_ro_lock);
2757 ASSERT(comp != NULL);
2758 NET_ADD_RECV_COMP(comp);
2759 return (comp->ro.failed);
2777 ASSERT(comp != NULL);
2778 mutex_enter(&comp->rw_ro_lock);
2779 comp->ro.shorted = shorted;
2780 mutex_exit(&comp->rw_ro_lock);
2791 ASSERT(comp != NULL);
2792 NET_ADD_RECV_COMP(comp);
2793 return (comp->ro.shorted);
2797 gen_set_random_param(elec_comp_t *comp,
double *param,
double norm_value,
2802 ASSERT(comp != NULL);
2803 ASSERT(param != NULL);
2804 ASSERT3F(stddev, >=, 0);
2807 new_param = norm_value + crc64_rand_normal(stddev);
2813 if (new_param > norm_value) {
2814 new_param = clamp(new_param, norm_value + 0.5 * stddev,
2815 norm_value + 1.5 * stddev);
2817 new_param = clamp(new_param, norm_value - 1.5 * stddev,
2818 norm_value - 0.5 * stddev);
2821 new_param = norm_value;
2823 mutex_enter(&comp->sys->worker_interlock);
2824 *param = norm_value;
2825 mutex_exit(&comp->sys->worker_interlock);
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));
2866 ASSERT(comp != NULL);
2867 ASSERT3U(comp->info->type, ==,
ELEC_GEN);
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));
2884 ASSERT(comp != NULL);
2885 ASSERT(!comp->sys->started);
2886 comp->info->userinfo = userinfo;
2897 ASSERT(comp != NULL);
2898 return (comp->info->userinfo);
2920 ASSERT(batt != NULL);
2921 ASSERT3U(batt->info->type, ==,
ELEC_BATT);
2922 ASSERT(!batt->sys->started);
2923 batt->info->batt.get_temp = cb;
2935 ASSERT(batt != NULL);
2936 ASSERT3U(batt->info->type, ==,
ELEC_BATT);
2937 return (batt->info->batt.get_temp);
2958 ASSERT(gen != NULL);
2959 ASSERT3U(gen->info->type, ==,
ELEC_GEN);
2960 ASSERT(!gen->sys->started);
2961 gen->info->gen.get_rpm = cb;
2972 ASSERT(gen != NULL);
2973 ASSERT3U(gen->info->type, ==,
ELEC_GEN);
2974 return (gen->info->gen.get_rpm);
2995 ASSERT(load != NULL);
2996 ASSERT3U(load->info->type, ==,
ELEC_LOAD);
2997 ASSERT(!load->sys->started);
2998 load->info->load.get_load = cb;
3009 ASSERT(load != NULL);
3010 ASSERT3U(load->info->type, ==,
ELEC_LOAD);
3011 return (load->info->load.get_load);
3015 update_short_leak_factor(elec_comp_t *comp,
double d_t)
3017 ASSERT(comp != NULL);
3018 ASSERT3F(d_t, >, 0);
3020 if (comp->rw.shorted) {
3026 if (comp->ro.in_pwr != 0)
3027 FILTER_IN(comp->rw.leak_factor, 0.99, d_t, 1);
3029 comp->rw.leak_factor = 0;
3031 comp->rw.leak_factor =
3032 wavg(0.97, 0.975, crc64_rand_fract());
3035 comp->rw.leak_factor = 0;
3040 network_reset(elec_sys_t *sys,
double d_t)
3042 ASSERT(sys != NULL);
3043 ASSERT_MUTEX_HELD(&sys->worker_interlock);
3044 ASSERT3F(d_t, >, 0);
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];
3050 memcpy(srcs_ext, comp->srcs, sizeof (srcs_ext));
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));
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));
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);
3076 comp->integ_mask = 0;
3077 switch (comp->info->type) {
3079 comp->load.seen =
false;
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);
3089 #ifdef LIBELEC_WITH_LIBSWITCH
3090 if (comp->scb.sw != NULL) {
3092 !libswitch_read(comp->scb.sw, NULL);
3095 comp->scb.wk_set = comp->scb.cur_set;
3104 network_update_gen(elec_comp_t *gen,
double d_t)
3106 ASSERT(gen != NULL);
3107 ASSERT(gen->info != NULL);
3108 ASSERT3U(gen->info->type, ==,
ELEC_GEN);
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);
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;
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);
3138 gen->gen.stab_factor_U = 1;
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);
3148 gen->gen.stab_factor_f = 1;
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;
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 /
3161 gen->gen.stab_factor_f * gen->gen.tgt_freq;
3164 gen->rw.out_volts = gen->rw.in_volts;
3165 gen->rw.out_freq = gen->rw.in_freq;
3167 gen->rw.in_volts = 0;
3168 gen->rw.in_freq = 0;
3169 gen->rw.out_volts = 0;
3170 gen->rw.out_freq = 0;
3175 network_update_batt(elec_comp_t *batt,
double d_t)
3177 double U, J, temp_coeff, J_max, I_max, I_rel;
3179 ASSERT(batt != NULL);
3180 ASSERT(batt->info != NULL);
3181 ASSERT3U(batt->info->type, ==,
ELEC_BATT);
3183 if (batt->info->batt.get_temp != NULL) {
3184 double T = batt->info->batt.get_temp(batt,
3185 batt->info->userinfo);
3187 mutex_enter(&batt->batt.lock);
3189 mutex_exit(&batt->batt.lock);
3191 temp_coeff = fx_lin_multi2(batt->batt.T, batt_temp_energy_curve,
3192 ARRAY_NUM_ELEM(batt_temp_energy_curve),
true);
3194 I_max = batt->info->batt.max_pwr / batt->info->batt.volts;
3195 I_rel = batt->batt.prev_amps / I_max;
3197 batt->batt.chg_rel, I_rel);
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;
3203 J += batt->batt.rechg_W * d_t;
3204 batt->batt.rechg_W = 0;
3207 if (!batt->rw.failed) {
3208 batt->rw.in_volts = U;
3209 batt->rw.out_volts = U;
3211 batt->rw.in_volts = 0;
3212 batt->rw.out_volts = 0;
3218 batt->batt.chg_rel = clamp(J / J_max, 0, 1);
3222 network_update_cb(elec_comp_t *cb,
double d_t)
3227 ASSERT(cb->info != NULL);
3228 ASSERT3U(cb->info->type, ==,
ELEC_CB);
3229 ASSERT3F(cb->info->cb.max_amps, >, 0);
3231 amps_rat = cb->rw.out_amps / cb->info->cb.max_amps;
3233 if (cb->info->cb.triphase)
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);
3238 if (cb->scb.temp >= 1.0) {
3239 cb->scb.wk_set =
false;
3240 cb->scb.cur_set =
false;
3241 #ifdef LIBELEC_WITH_LIBSWITCH
3243 if (cb->scb.sw != NULL)
3244 libswitch_set(cb->scb.sw,
true);
3246 if (cb->info->cb.fuse)
3247 cb->rw.failed =
true;
3252 network_update_tru(elec_comp_t *tru,
double d_t)
3254 ASSERT(tru != NULL);
3255 ASSERT(tru->info != NULL);
3256 ASSERT3U(tru->info->type, ==,
ELEC_TRU);
3257 ASSERT3F(d_t, >, 0);
3259 if (tru->ro.in_volts < tru->info->tru.min_volts) {
3263 if (!tru->info->tru.charger) {
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);
3291 }
else if (regul_tgt > tru->tru.regul) {
3292 FILTER_IN(tru->tru.regul, regul_tgt, d_t, 1);
3294 FILTER_IN(tru->tru.regul, regul_tgt, d_t, 2 * d_t);
3300 network_srcs_update(elec_sys_t *sys,
double d_t)
3302 ASSERT(sys != NULL);
3303 ASSERT_MUTEX_HELD(&sys->worker_interlock);
3304 ASSERT3F(d_t, >, 0);
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) {
3311 network_update_batt(comp, d_t);
3314 network_update_gen(comp, d_t);
3317 network_update_tru(comp, d_t);
3326 network_loads_randomize(elec_sys_t *sys,
double d_t)
3328 ASSERT(sys != NULL);
3329 ASSERT3F(d_t, >, 0);
3331 for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
3332 comp = list_next(&sys->comps, comp)) {
3334 FILTER_IN(comp->load.random_load_factor,
3335 clamp(1.0 + crc64_rand_normal(0.1), 0.8, 1.2),
3342 load_incap_update(elec_comp_t *comp,
double d_t)
3347 ASSERT(comp != NULL);
3348 ASSERT(comp->info != NULL);
3349 ASSERT3U(comp->info->type, ==,
ELEC_LOAD);
3350 ASSERT3F(d_t, >, 0);
3353 if (info->load.incap_C == 0)
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;
3364 network_loads_update(elec_sys_t *sys,
double d_t)
3366 ASSERT(sys != NULL);
3367 ASSERT3F(d_t, >, 0);
3369 for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
3370 comp = list_next(&sys->comps, comp)) {
3371 ASSERT(comp->info != NULL);
3373 if (comp->info->type ==
ELEC_CB) {
3374 network_update_cb(comp, d_t);
3375 }
else if (comp->info->type ==
ELEC_LOAD) {
3381 if (!comp->load.seen)
3382 network_load_integrate_load(NULL, comp, 0, d_t);
3383 load_incap_update(comp, d_t);
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;
3392 network_ties_update(elec_sys_t *sys)
3394 ASSERT(sys != NULL);
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]) {
3404 if (n_tied == ARRAY_NUM_ELEM(tied))
3410 sum_link_amps(&comp->links[tied[0]]),
3411 sum_link_amps(&comp->links[tied[1]])
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;
3421 add_src_up(elec_comp_t *comp, elec_comp_t *src,
const elec_comp_t *upstream)
3423 ASSERT(comp != NULL);
3424 ASSERT(src != NULL);
3425 ASSERT(upstream != NULL);
3427 ASSERT0(src->src_mask & (1 << src->src_idx));
3428 ASSERT3U(comp->n_srcs, <, ELEC_MAX_SRCS);
3429 comp->srcs[comp->n_srcs] = src;
3431 ASSERT3F(src->info->int_R, >, 0);
3432 comp->src_int_cond_total += (1.0 / src->info->int_R) *
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;
3444 network_paint_src_bus(elec_comp_t *src, elec_comp_t *upstream,
3445 elec_comp_t *comp,
unsigned depth)
3447 ASSERT(src != NULL);
3448 ASSERT(upstream != NULL);
3449 ASSERT(comp != NULL);
3450 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3452 if (comp->rw.failed)
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;
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);
3471 network_paint_src_tie(elec_comp_t *src, elec_comp_t *upstream,
3472 elec_comp_t *comp,
unsigned depth)
3474 bool found =
false, tied =
false;
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);
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;
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);
3511 recalc_out_volts_tru(elec_comp_t *comp)
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);
3520 recalc_out_volts_freq_inv(elec_comp_t *comp)
3522 double mult_U, mult_f;
3524 ASSERT(comp != NULL);
3525 ASSERT3U(comp->info->type, ==,
ELEC_INV);
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;
3536 network_paint_src_tru_inv(elec_comp_t *src, elec_comp_t *upstream,
3537 elec_comp_t *comp,
unsigned depth)
3539 ASSERT(src != NULL);
3540 ASSERT(upstream != NULL);
3541 ASSERT(comp != NULL);
3542 ASSERT(comp->info != NULL);
3543 ASSERT(comp->info->type ==
ELEC_TRU ||
3545 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3548 if (upstream != comp->links[0].comp)
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);
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;
3564 recalc_out_volts_tru(comp);
3566 recalc_out_volts_freq_inv(comp);
3569 comp->rw.in_volts = 0;
3570 comp->rw.in_freq = 0;
3571 comp->rw.out_volts = 0;
3572 comp->rw.out_freq = 0;
3574 ASSERT(comp->links[1].comp != NULL);
3578 if (comp->rw.out_volts != 0) {
3579 network_paint_src_comp(comp, comp,
3580 comp->links[1].comp, depth + 1);
3585 network_paint_src_xfrmr(elec_comp_t *src, elec_comp_t *upstream,
3586 elec_comp_t *comp,
unsigned depth)
3588 ASSERT(src != NULL);
3589 ASSERT(upstream != NULL);
3590 ASSERT(comp != NULL);
3591 ASSERT(comp->info != NULL);
3593 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3596 if (upstream != comp->links[0].comp)
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);
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;
3615 comp->rw.in_volts = 0;
3616 comp->rw.out_volts = 0;
3617 comp->rw.in_freq = 0;
3618 comp->rw.out_freq = 0;
3620 ASSERT(comp->links[1].comp != NULL);
3624 if (comp->rw.out_volts != 0) {
3625 network_paint_src_comp(comp, comp,
3626 comp->links[1].comp, depth + 1);
3631 network_paint_src_scb(elec_comp_t *src, elec_comp_t *upstream,
3632 elec_comp_t *comp,
unsigned depth)
3634 ASSERT(src != NULL);
3635 ASSERT(upstream != NULL);
3636 ASSERT(comp != NULL);
3637 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
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;
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);
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);
3661 network_paint_src_diode(elec_comp_t *src, elec_comp_t *upstream,
3662 elec_comp_t *comp,
unsigned depth)
3664 ASSERT(src != NULL);
3665 ASSERT(upstream != NULL);
3666 ASSERT(comp != NULL);
3667 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
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;
3676 comp->rw.in_volts = 0;
3678 network_paint_src_comp(src, comp, comp->links[1].comp,
3684 network_paint_src_comp(elec_comp_t *src, elec_comp_t *upstream,
3685 elec_comp_t *comp,
unsigned depth)
3687 ASSERT(src != NULL);
3688 ASSERT(src->info != NULL);
3689 ASSERT(upstream != NULL);
3690 ASSERT(comp != NULL);
3691 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3693 switch (comp->info->type) {
3695 if (src != comp && comp->rw.out_volts < src->rw.out_volts)
3696 add_src_up(comp, src, upstream);
3703 ASSERT(!comp->info->bus.ac);
3705 ASSERT3U(src_is_AC(src->info), ==, comp->info->bus.ac);
3707 network_paint_src_bus(src, upstream, comp, depth);
3711 network_paint_src_tru_inv(src, upstream, comp, depth);
3714 network_paint_src_xfrmr(src, upstream, comp, depth);
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;
3724 comp->rw.in_volts = 0;
3725 comp->rw.in_freq = 0;
3730 network_paint_src_scb(src, upstream, comp, depth);
3733 network_paint_src_tie(src, upstream, comp, depth);
3736 network_paint_src_diode(src, upstream, comp, depth);
3744 network_paint(elec_sys_t *sys)
3746 ASSERT(sys != NULL);
3747 ASSERT_MUTEX_HELD(&sys->worker_interlock);
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);
3753 comp->info->type ==
ELEC_GEN) && comp->rw.out_volts != 0) {
3754 network_paint_src_comp(comp, comp,
3755 comp->links[0].comp, 0);
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)
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);
3771 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3773 if (upstream != comp->links[0].comp)
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;
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;
3797 return (comp->rw.in_amps);
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)
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);
3811 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
3813 if (upstream != comp->links[0].comp)
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;
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;
3831 return (comp->rw.in_amps);
3835 network_load_integrate_load(
const elec_comp_t *src, elec_comp_t *comp,
3836 unsigned depth,
double d_t)
3838 double load_WorI, load_I, in_volts_net, incap_I, src_fract;
3842 ASSERT(comp != NULL);
3843 ASSERT(comp->info != NULL);
3844 ASSERT3U(comp->info->type, ==,
ELEC_LOAD);
3852 in_volts_net = MAX(comp->rw.in_volts, comp->load.incap_U);
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);
3865 ASSERT3F(load_WorI, >=, 0);
3867 load_WorI *= comp->load.random_load_factor;
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;
3879 if (comp->rw.shorted) {
3883 ASSERT3F(comp->rw.leak_factor, <, 1);
3884 load_I /= (1 - comp->rw.leak_factor);
3885 }
else if (comp->rw.failed) {
3895 if (info->load.incap_C > 0 &&
3896 comp->rw.in_volts > comp->load.incap_U + 0.01) {
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))));
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;
3943 incap_I = Q_delta / d_t;
3965 if (comp->load.incap_U > comp->rw.in_volts) {
3967 double load_Q = load_I * d_t;
3969 double avail_Q = (comp->load.incap_U - comp->rw.in_volts) *
3972 double used_Q = MIN(load_Q, avail_Q);
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;
3987 comp->rw.out_volts = 0;
3988 comp->load.incap_d_Q = -used_Q;
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;
4000 ASSERT(!isnan(comp->rw.out_amps));
4001 ASSERT(!isnan(comp->rw.out_volts));
4002 comp->load.seen =
true;
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);
4011 return (comp->rw.in_amps * src_fract);
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)
4018 double out_amps_total = 0;
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);
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];
4036 out_amps_total /= (1 - comp->rw.leak_factor);
4037 return (out_amps_total);
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)
4044 double out_amps_total = 0;
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);
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])
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];
4067 return (out_amps_total);
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)
4074 ASSERT(src != NULL);
4075 ASSERT(upstream != NULL);
4076 ASSERT(comp != NULL);
4077 ASSERT(comp->info != NULL);
4079 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
4081 if (!comp->scb.wk_set)
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;
4093 return (comp->links[!i].out_amps[src->src_idx]);
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)
4103 ASSERT(src != NULL);
4104 ASSERT(batt != NULL);
4105 ASSERT(batt->info != NULL);
4106 ASSERT3U(batt->info->type, ==,
ELEC_BATT);
4108 if (depth != 0 && check_upstream(batt, src, upstream)) {
4109 double U_delta = MAX(src->rw.out_volts - batt->rw.out_volts, 0);
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;
4122 batt->batt.rechg_W = batt->rw.in_volts *
4125 batt->rw.out_amps = 0;
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;
4133 return (batt->rw.out_amps);
4140 network_load_integrate_gen(elec_comp_t *gen,
unsigned depth,
double d_t)
4144 ASSERT(gen != NULL);
4145 ASSERT(gen->info != NULL);
4146 ASSERT3U(gen->info->type, ==,
ELEC_GEN);
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;
4159 return (gen->rw.out_amps);
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)
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);
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));
4179 return (comp->links[1].out_amps[src->src_idx]);
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)
4186 ASSERT(src != NULL);
4187 ASSERT(src->info != NULL);
4191 ASSERT(comp != NULL);
4192 ASSERT(comp->info != NULL);
4193 ASSERT3U(depth, <, MAX_NETWORK_DEPTH);
4194 ASSERT3F(d_t, >, 0);
4196 if (comp != src && !check_upstream(comp, src, upstream))
4199 switch (comp->info->type) {
4201 return (network_load_integrate_batt(src, upstream, comp,
4204 return (network_load_integrate_gen(comp, depth, d_t));
4207 ASSERT3P(upstream, ==, comp->links[0].comp);
4208 return (network_load_integrate_tru_inv(src, upstream, comp,
4211 ASSERT3P(upstream, ==, comp->links[0].comp);
4212 return (network_load_integrate_xfrmr(src, upstream, comp,
4215 return (network_load_integrate_load(src, comp, depth, d_t));
4217 return (network_load_integrate_bus(src, upstream, comp,
4221 return (network_load_integrate_scb(src, upstream, comp,
4224 return (network_load_integrate_tie(src, upstream, comp,
4227 return (network_load_integrate_diode(src, upstream, comp,
4236 network_load_integrate(elec_sys_t *sys,
double d_t)
4238 ASSERT(sys != NULL);
4239 ASSERT3F(d_t, >, 0);
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,
4249 network_state_xfer(elec_sys_t *sys)
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);
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);
4265 mk_spaces(
char *spaces,
unsigned len)
4267 memset(spaces, 0, len);
4268 for (
unsigned i = 0; i + 1 < len; i += 2) {
4271 spaces[i + 1] =
' ';
4273 spaces[i + 1] =
'-';
4278 print_trace_data(
const elec_comp_t *comp,
unsigned depth,
bool out_data,
4284 ASSERT(comp != NULL);
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);
4297 network_trace_scb(
const elec_comp_t *upstream,
const elec_comp_t *comp,
4298 unsigned depth,
bool do_print)
4300 ASSERT(upstream != NULL);
4301 ASSERT(comp != NULL);
4304 if (!comp->scb.wk_set)
4306 if (upstream == comp->links[0].comp) {
4307 return (network_trace(comp, comp->links[1].comp,
4308 depth + 1, do_print));
4310 return (network_trace(comp, comp->links[0].comp,
4311 depth + 1, do_print));
4316 network_trace(
const elec_comp_t *upstream,
const elec_comp_t *comp,
4317 unsigned depth,
bool do_print)
4319 double load_trace = 0;
4321 ASSERT(upstream != NULL);
4322 ASSERT(comp != NULL);
4324 switch (comp->info->type) {
4326 load_trace = network_trace(comp, comp->links[0].comp, depth + 1,
4328 load_trace += comp->rw.out_volts * comp->rw.in_amps;
4330 if (upstream == comp) {
4331 print_trace_data(comp, depth,
true, load_trace);
4333 print_trace_data(comp, depth,
false,
4336 network_trace(comp, comp->links[0].comp, depth + 1,
4341 load_trace = network_trace(comp, comp->links[0].comp,
4344 print_trace_data(comp, depth,
true, load_trace);
4345 network_trace(comp, comp->links[0].comp, depth + 1,
4352 if (upstream != comp) {
4354 print_trace_data(comp, depth,
false, 0);
4355 return (comp->rw.in_volts * comp->rw.in_amps);
4357 load_trace = network_trace(comp,
4358 comp->links[0].comp, depth + 1,
false);
4360 print_trace_data(comp, depth,
true, load_trace);
4361 network_trace(comp, comp->links[0].comp,
4368 print_trace_data(comp, depth,
false, 0);
4369 return (comp->rw.in_volts * comp->rw.in_amps);
4371 for (
unsigned i = 0; i < comp->n_links; i++) {
4372 load_trace += network_trace(comp, comp->links[i].comp,
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,
4385 load_trace = network_trace_scb(upstream, comp, depth,
false);
4387 print_trace_data(comp, depth,
false, load_trace);
4388 network_trace_scb(upstream, comp, depth,
true);
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);
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,
4410 load_trace = network_trace(comp, comp->links[1].comp,
4413 print_trace_data(comp, depth,
false, load_trace);
4414 network_trace(comp, comp->links[1].comp,
4422 return (load_trace);
4426 elec_sys_worker(
void *userinfo)
4429 uint64_t now = microclock();
4431 #ifdef LIBELEC_TIMING_DEBUG
4432 static int steps = 0;
4433 static double d_t_total = 0;
4434 static uint64_t last_report = 0;
4437 ASSERT(userinfo != NULL);
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;
4446 d_t = USEC2SEC(now - sys->prev_clock) * sys->time_factor;
4447 sys->prev_clock = now;
4448 mutex_exit(&sys->paused_lock);
4450 #ifdef LIBELEC_TIMING_DEBUG
4453 if (now - last_report > SEC2USEC(1)) {
4454 logMsg(
"steps: %4d d_t_avg: %f", steps, d_t_total / steps);
4465 #ifdef LIBELEC_WITH_NETLINK
4466 if (sys->net_recv.active) {
4467 elec_net_recv_update(sys);
4471 mutex_enter(&sys->worker_interlock);
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)) {
4477 ASSERT(ucbi->cb != NULL);
4478 ucbi->cb(sys,
true, ucbi->userinfo);
4481 mutex_exit(&sys->user_cbs_lock);
4483 network_reset(sys, d_t);
4484 network_srcs_update(sys, d_t);
4485 network_loads_randomize(sys, d_t);
4487 network_load_integrate(sys, d_t);
4488 network_loads_update(sys, d_t);
4489 network_ties_update(sys);
4494 network_state_xfer(sys);
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)) {
4500 ASSERT(ucbi->cb != NULL);
4501 ucbi->cb(sys,
false, ucbi->userinfo);
4504 mutex_exit(&sys->user_cbs_lock);
4506 mutex_exit(&sys->worker_interlock);
4508 #ifdef LIBELEC_WITH_NETLINK
4509 elec_net_send_update(sys);
4515 comp_free(elec_comp_t *comp)
4517 ASSERT(comp != NULL);
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);
4530 mutex_destroy(&comp->batt.lock);
4531 else if (comp->info->type ==
ELEC_GEN)
4532 mutex_destroy(&comp->gen.lock);
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);
4540 mutex_destroy(&comp->rw_ro_lock);
4542 memset(comp, 0,
sizeof (*comp));
4553 ASSERT(comp != NULL);
4554 ASSERT(comp->info != NULL);
4555 ASSERT3U(comp->info->type, ==,
ELEC_CB);
4560 comp->scb.cur_set = set;
4571 ASSERT(comp != NULL);
4572 ASSERT(comp->info != NULL);
4573 ASSERT3U(comp->info->type, ==,
ELEC_CB);
4575 return (comp->scb.cur_set);
4586 ASSERT(comp != NULL);
4587 ASSERT(comp->info != NULL);
4588 ASSERT3U(comp->info->type, ==,
ELEC_CB);
4590 return (comp->scb.temp);
4606 elec_comp_t *
const bus_list[STATIC_ARRAY_LEN_ARG(list_len)])
4610 ASSERT(comp != NULL);
4611 ASSERT(comp->info != NULL);
4612 ASSERT3U(comp->info->type, ==,
ELEC_TIE);
4613 ASSERT(bus_list != NULL || list_len == 0);
4616 if (comp->ro.failed)
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);
4628 new_state = safe_calloc(comp->n_links, sizeof (*new_state));
4629 for (
size_t i = 0; i < list_len; i++) {
4631 for (j = 0; j < comp->n_links; j++) {
4632 if (comp->links[j].comp == bus_list[i]) {
4633 new_state[j] =
true;
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);
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);
4663 ASSERT(comp != NULL);
4679 elec_comp_t **bus_list;
4682 ASSERT(comp != NULL);
4683 ASSERT(comp->sys != NULL);
4684 ASSERT(comp->info != NULL);
4685 ASSERT3U(comp->info->type, ==,
ELEC_TIE);
4687 if (comp->ro.failed)
4691 for (len = 0; va_arg(ap2, elec_comp_t *) != NULL; len++)
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 *);
4715 ASSERT(comp != NULL);
4716 ASSERT(comp->info != NULL);
4717 ASSERT3U(comp->info->type, ==,
ELEC_TIE);
4719 if (comp->ro.failed)
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);
4742 ASSERT(comp != NULL);
4743 ASSERT(comp->info != NULL);
4744 ASSERT3U(comp->info->type, ==,
ELEC_TIE);
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);
4769 elec_comp_t *bus_list[STATIC_ARRAY_LEN_ARG(cap)])
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);
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;
4784 mutex_exit(&comp->tie.lock);
4801 ASSERT(comp != NULL);
4802 ASSERT(comp->info != NULL);
4803 ASSERT3U(comp->info->type, ==,
ELEC_TIE);
4805 return (comp->n_links);
4830 ASSERT(tie != NULL);
4831 ASSERT3U(tie->info->type, ==,
ELEC_TIE);
4832 va_start(ap, exhaustive);
4847 size_t list_len = 0, n_tied = 0;
4848 const elec_comp_t *bus;
4850 ASSERT(tie != NULL);
4851 ASSERT3U(tie->info->type, ==,
ELEC_TIE);
4853 mutex_enter(&tie->tie.lock);
4854 for (
unsigned i = 0; i < tie->n_links; i++) {
4855 if (tie->tie.cur_state[i])
4858 for (list_len = 0; (bus = va_arg(ap,
const elec_comp_t *)) != NULL;
4861 for (
unsigned i = 0; i < tie->n_links; i++) {
4862 if (tie->links[i].comp == bus) {
4864 if (!tie->tie.cur_state[i]) {
4873 if (list_len != n_tied && exclusive)
4876 mutex_exit(&tie->tie.lock);
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);
4907 mutex_exit(&gen->gen.lock);
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);
4928 mutex_exit(&((elec_comp_t *)gen)->gen.lock);
4940 ASSERT(batt != NULL);
4941 ASSERT(batt->info != NULL);
4942 ASSERT3U(batt->info->type, ==,
ELEC_BATT);
4943 return (batt->batt.chg_rel);
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);
4961 mutex_enter(&batt->sys->worker_interlock);
4962 batt->batt.chg_rel = chg_rel;
4964 batt->batt.rechg_W = 0;
4965 mutex_exit(&batt->sys->worker_interlock);
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);
4982 mutex_exit(&((elec_comp_t *)batt)->batt.lock);
4996 ASSERT(batt != NULL);
4997 ASSERT(batt->info != NULL);
4998 ASSERT3U(batt->info->type, ==,
ELEC_BATT);
5000 mutex_enter(&batt->batt.lock);
5002 mutex_exit(&batt->batt.lock);
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 &&
5035 static const vect2_t chg_volt_curve[] = {
5047 ASSERT3F(U_nominal, >, 0);
5048 ASSERT3F(chg_rel, >=, 0);
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));
5060 #ifdef LIBELEC_WITH_NETLINK
5065 ASSERT(sys != NULL);
5066 ASSERT_MUTEX_HELD(&sys->worker_interlock);
5067 ASSERT(conn != NULL);
5069 list_remove(&sys->net_send.conns_list, conn);
5070 htbl_remove(&sys->net_send.conns, &conn->conn_id,
false);
5077 conn_add_notif(netlink_conn_id_t conn_id, netlink_conn_ev_t ev,
void *userinfo)
5083 ASSERT(userinfo != NULL);
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);
5093 conn_rem_notif(netlink_conn_id_t conn_id, netlink_conn_ev_t ev,
5100 ASSERT(userinfo != NULL);
5103 mutex_enter(&sys->worker_interlock);
5104 conn = htbl_lookup(&sys->net_send.conns, &conn_id);
5106 kill_conn(sys, conn);
5107 mutex_exit(&sys->worker_interlock);
5111 libelec_enable_net_send(elec_sys_t *sys)
5113 ASSERT(sys != NULL);
5114 ASSERT(!sys->started);
5115 ASSERT(!sys->net_send.active);
5116 ASSERT(!sys->net_recv.active);
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),
5122 list_create(&sys->net_send.conns_list, sizeof (
net_conn_t),
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);
5134 net_conn_free(
void *net_conn,
void *unused)
5138 ASSERT(net_conn != NULL);
5146 libelec_disable_net_send(elec_sys_t *sys)
5148 ASSERT(sys != NULL);
5149 ASSERT(!sys->started);
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)
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;
5163 libelec_enable_net_recv(elec_sys_t *sys)
5165 ASSERT(sys != NULL);
5166 ASSERT(!sys->started);
5167 ASSERT(!sys->net_send.active);
5168 ASSERT(!sys->net_recv.active);
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;
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;
5181 netlink_add_proto(&sys->net_recv.proto);
5185 libelec_disable_net_recv(elec_sys_t *sys)
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;
5198 get_net_conn(elec_sys_t *sys, netlink_conn_id_t conn_id)
5202 ASSERT(sys != NULL);
5203 ASSERT_MUTEX_HELD(&sys->worker_interlock);
5205 conn = htbl_lookup(&sys->net_send.conns, &conn_id);
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);
5221 ASSERT(sys != NULL);
5222 ASSERT(conn != NULL);
5223 ASSERT(req != NULL);
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))
5231 DELAY_LINE_PUSH_IMM(&conn->kill_delay,
false);
5235 netlink_send_msg_notif(netlink_conn_id_t conn_id,
const void *buf,
size_t sz,
5242 ASSERT(buf != NULL);
5243 ASSERT(userinfo != NULL);
5247 logMsg(
"Received bad packet length %d", (
int)sz);
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);
5256 mutex_enter(&sys->worker_interlock);
5257 conn = get_net_conn(sys, conn_id);
5258 if (req->req == NET_REQ_MAP) {
5261 if (map->conf_crc == sys->conf_crc &&
5263 handle_net_req_map(sys, conn, buf);
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);
5271 logMsg(
"Cannot handle net map req, elec file "
5276 logMsg(
"Unknown or malformed req %x of length %d",
5279 mutex_exit(&sys->worker_interlock);
5283 send_xmit_data_conn(elec_sys_t *sys,
net_conn_t *conn)
5288 ASSERT(sys != NULL);
5289 ASSERT_MUTEX_HELD(&sys->worker_interlock);
5290 ASSERT(conn != NULL);
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];
5301 if (NETMAPGET(conn->map, i)) {
5303 ASSERT3U(rep->n_comps, <=, conn->num_active);
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);
5326 ASSERT3U(rep->n_comps, ==, conn->num_active);
5327 (void)netlink_sendto(NETLINK_PROTO_LIBELEC, rep, repsz,
5332 elec_net_send_update(elec_sys_t *sys)
5334 ASSERT(sys != NULL);
5336 sys->net_send.xmit_ctr = (sys->net_send.xmit_ctr + 1) % NET_XMIT_INTVAL;
5337 if (sys->net_send.xmit_ctr != 0)
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);
5349 mutex_exit(&sys->worker_interlock);
5355 ASSERT(sys != NULL);
5356 ASSERT(comps != NULL);
5358 NET_DBG_LOG(
"New dev data with %d comps", (
int)comps->n_comps);
5360 for (
unsigned i = 0; i < comps->n_comps; i++) {
5364 if (data->idx >= list_count(&sys->comps))
5366 comp = sys->comps_array[data->idx];
5368 mutex_enter(&comp->rw_ro_lock);
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);
5382 comp->ro = comp->rw;
5384 mutex_exit(&comp->rw_ro_lock);
5389 netlink_recv_msg_notif(netlink_conn_id_t conn_id,
const void *buf,
size_t sz,
5397 ASSERT(buf != NULL);
5400 ASSERT(userinfo != NULL);
5404 logMsg(
"Received bad packet length %d", (
int)sz);
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);
5412 if (rep->rep == NET_REP_COMPS &&
5416 if (rep_comps->conf_crc == sys->conf_crc) {
5417 handle_net_rep_comps(sys, rep_comps);
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);
5425 logMsg(
"Cannot handle rep COMPS, elec file "
5430 logMsg(
"Unknown or malformed rep %x of length %d",
5436 elec_net_recv_update(elec_sys_t *sys)
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);
5448 send_net_recv_map(elec_sys_t *sys)
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);
5468 net_add_recv_comp(elec_comp_t *comp)
5472 ASSERT(comp != NULL);
5474 if (!sys->net_recv.active)
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;
5483 mutex_exit(&sys->worker_interlock);
double libelec_batt_get_chg_rel(const elec_comp_t *batt)
bool libelec_comp_is_AC(const elec_comp_t *comp)
double libelec_gen_set_random_freq(elec_comp_t *comp, double stddev)
void libelec_comp_set_userinfo(elec_comp_t *comp, void *userinfo)
double libelec_comp_get_out_amps(const elec_comp_t *comp)
void libelec_gen_set_rpm_cb(elec_comp_t *gen, elec_get_rpm_cb_t cb)
void libelec_sys_set_time_factor(elec_sys_t *sys, double time_factor)
elec_get_rpm_cb_t libelec_gen_get_rpm_cb(const elec_comp_t *gen)
double(* elec_get_load_cb_t)(elec_comp_t *comp, void *userinfo)
elec_comp_t * libelec_comp_find(elec_sys_t *sys, const char *name)
elec_get_load_cb_t libelec_load_get_load_cb(elec_comp_t *load)
double libelec_comp_get_out_freq(const elec_comp_t *comp)
void libelec_tie_set(elec_comp_t *comp,...) SENTINEL_ATTR
double libelec_phys_get_batt_voltage(double U_nominal, double chg_rel, double I_rel)
double(* elec_get_temp_cb_t)(elec_comp_t *comp, void *userinfo)
double libelec_comp_get_eff(const elec_comp_t *gen)
void libelec_walk_comps(const elec_sys_t *sys, void(*cb)(elec_comp_t *, void *), void *userinfo)
const elec_comp_info_t * libelec_get_comp_infos(const elec_sys_t *sys, size_t *num_infos)
bool libelec_cb_get(const elec_comp_t *comp)
void libelec_sys_stop(elec_sys_t *sys)
bool libelec_comp_get_failed(const elec_comp_t *comp)
double libelec_comp_get_out_pwr(const elec_comp_t *comp)
const elec_comp_info_t * libelec_comp2info(const elec_comp_t *comp)
bool libelec_comp_get_shorted(const elec_comp_t *comp)
double libelec_batt_get_temp(const elec_comp_t *batt)
void libelec_batt_set_temp_cb(elec_comp_t *batt, elec_get_temp_cb_t cb)
double libelec_comp_get_in_freq(const elec_comp_t *comp)
bool libelec_deserialize(elec_sys_t *sys, const conf_t *ser, const char *prefix)
void libelec_load_set_load_cb(elec_comp_t *load, elec_get_load_cb_t cb)
bool libelec_comp_is_powered(const elec_comp_t *comp)
elec_sys_t * libelec_new(const char *filename)
double libelec_comp_get_in_volts(const elec_comp_t *comp)
bool libelec_tie_get(elec_comp_t *tie, bool_t exhaustive,...) SENTINEL_ATTR
double libelec_comp_get_in_pwr(const elec_comp_t *comp)
void libelec_batt_set_temp(elec_comp_t *batt, double T)
size_t libelec_tie_get_num_buses(const elec_comp_t *comp)
void libelec_comp_set_shorted(elec_comp_t *comp, bool shorted)
void libelec_add_user_cb(elec_sys_t *sys, bool pre, elec_user_cb_t cb, void *userinfo)
void * libelec_comp_get_userinfo(const elec_comp_t *comp)
double libelec_gen_get_rpm(const elec_comp_t *gen)
void libelec_tie_set_list(elec_comp_t *comp, size_t list_len, elec_comp_t *const bus_list[])
double libelec_comp_get_in_amps(const elec_comp_t *comp)
bool libelec_sys_is_started(const elec_sys_t *sys)
void libelec_tie_set_all(elec_comp_t *comp, bool tied)
double libelec_cb_get_temp(const elec_comp_t *comp)
void libelec_batt_set_chg_rel(elec_comp_t *batt, double chg_rel)
double libelec_gen_set_random_volts(elec_comp_t *comp, double stddev)
bool libelec_chgr_get_working(const elec_comp_t *chgr)
size_t libelec_tie_get_list(elec_comp_t *comp, size_t cap, elec_comp_t *bus_list[])
elec_comp_t * libelec_comp_get_conn(const elec_comp_t *comp, size_t i)
void(* elec_user_cb_t)(elec_sys_t *sys, bool pre, void *userinfo)
double libelec_comp_get_out_volts(const elec_comp_t *comp)
double(* elec_get_rpm_cb_t)(elec_comp_t *comp, void *userinfo)
double libelec_comp_get_incap_volts(const elec_comp_t *comp)
void libelec_serialize(elec_sys_t *sys, conf_t *ser, const char *prefix)
size_t libelec_comp_get_num_conns(const elec_comp_t *comp)
void libelec_tie_set_v(elec_comp_t *comp, va_list ap)
elec_get_temp_cb_t libelec_batt_get_temp_cb(const elec_comp_t *batt)
void libelec_comp_set_failed(elec_comp_t *comp, bool failed)
void libelec_destroy(elec_sys_t *sys)
void libelec_gen_set_rpm(elec_comp_t *gen, double rpm)
bool libelec_sys_start(elec_sys_t *sys)
bool libelec_sys_can_start(const elec_sys_t *sys)
double libelec_sys_get_time_factor(const elec_sys_t *sys)
bool libelec_tie_get_all(elec_comp_t *comp)
void libelec_cb_set(elec_comp_t *comp, bool set)
void libelec_remove_user_cb(elec_sys_t *sys, bool pre, elec_user_cb_t cb, void *userinfo)
bool libelec_tie_get_v(elec_comp_t *tie, bool exhaustive, va_list ap)