Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
717 changes: 717 additions & 0 deletions lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,8 @@ lib_deps =
ESPAsyncTCP @ 1.2.2
ESPAsyncUDP
;; makuna/NeoPixelBus @ 2.6.9 ;; WLEDMM use if you have problems with 2.7.5
makuna/NeoPixelBus @ 2.7.5
makuna/NeoPixelBus @ 2.7.9 ;; 2.7.9 has improved stability of bitbang outputs on 8266
ESP8266PWM
${env.lib_deps}

;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48
Expand Down
37 changes: 37 additions & 0 deletions tools/stress_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
# Some web server stress tests
#
# Perform a large number of parallel requests, stress testing the web server
# TODO: some kind of performance metrics

# Accepts three command line arguments:
# - first argument - mandatory - IP or hostname of target server
# - second argument - target type (optional)
# - third argument - xfer count (for replicated targets) (optional)
HOST=$1
declare -n TARGET_STR="${2:-JSON_LARGER}_TARGETS"
REPLICATE_COUNT=$(("${3:-10}"))

PARALLEL_MAX=${PARALLEL_MAX:-50}

CURL_ARGS="--compressed --parallel --parallel-immediate --parallel-max ${PARALLEL_MAX}"
CURL_PRINT_RESPONSE_ARGS="-w %{http_code}\n"

JSON_TARGETS=('json/state' 'json/info' 'json/si', 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Comma in Bash array splits an element; remove it

This makes 'json/si,' a literal entry.

-JSON_TARGETS=('json/state' 'json/info' 'json/si', 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
+JSON_TARGETS=('json/state' 'json/info' 'json/si' 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
JSON_TARGETS=('json/state' 'json/info' 'json/si', 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
JSON_TARGETS=('json/state' 'json/info' 'json/si' 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 20-20: JSON_TARGETS appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 20-20: Use spaces, not commas, to separate array elements.

(SC2054)

🤖 Prompt for AI Agents
In tools/stress_test.sh around line 20, the Bash array contains a stray comma
causing the element 'json/si,' to include the comma; remove the comma so entries
are space-separated (e.g., 'json/si' 'json/palettes') ensuring no punctuation
inside the quoted elements and then save the file.

FILE_TARGETS=('' 'iro.js' 'rangetouch.js' 'settings' 'settings/wifi')
# Replicate one target many times
function replicate() {
printf "${1}?%d " $(seq 1 ${REPLICATE_COUNT})
}
read -a JSON_TINY_TARGETS <<< $(replicate "json/nodes")
read -a JSON_SMALL_TARGETS <<< $(replicate "json/info")
read -a JSON_LARGE_TARGETS <<< $(replicate "json/si")
read -a JSON_LARGER_TARGETS <<< $(replicate "json/fxdata")

# Expand target URLS to full arguments for curl
TARGETS=(${TARGET_STR[@]})
#echo "${TARGETS[@]}"
FULL_TGT_OPTIONS=$(printf "http://${HOST}/%s -o /dev/null " "${TARGETS[@]}")
#echo ${FULL_TGT_OPTIONS}

time curl ${CURL_ARGS} ${FULL_TGT_OPTIONS}
46 changes: 46 additions & 0 deletions tools/udp_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import numpy as np
import socket

class WledRealtimeClient:
def __init__(self, wled_controller_ip, num_pixels, udp_port=21324, max_pixels_per_packet=126):
self.wled_controller_ip = wled_controller_ip
self.num_pixels = num_pixels
self.udp_port = udp_port
self.max_pixels_per_packet = max_pixels_per_packet
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._prev_pixels = np.full((3, self.num_pixels), 253, dtype=np.uint8)
self.pixels = np.full((3, self.num_pixels), 1, dtype=np.uint8)

def update(self):
# Truncate values and cast to integer
self.pixels = np.clip(self.pixels, 0, 255).astype(np.uint8)
p = np.copy(self.pixels)

idx = np.where(~np.all(p == self._prev_pixels, axis=0))[0]
num_pixels = len(idx)
n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
idx_split = np.array_split(idx, n_packets)

header = bytes([1, 2]) # WARLS protocol header
for packet_indices in idx_split:
data = bytearray(header)
for i in packet_indices:
data.extend([i, *p[:, i]]) # Index and RGB values
self._sock.sendto(bytes(data), (self.wled_controller_ip, self.udp_port))
Comment on lines +21 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard zero-changes path; array_split(0) raises

When no pixels change, n_packets becomes 0 and np.array_split() throws.

-        n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
-        idx_split = np.array_split(idx, n_packets)
+        if num_pixels == 0:
+            return
+        n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
+        idx_split = np.array_split(idx, n_packets)

Also consider early-return before building header for the no-change case.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
idx_split = np.array_split(idx, n_packets)
header = bytes([1, 2]) # WARLS protocol header
for packet_indices in idx_split:
data = bytearray(header)
for i in packet_indices:
data.extend([i, *p[:, i]]) # Index and RGB values
self._sock.sendto(bytes(data), (self.wled_controller_ip, self.udp_port))
if num_pixels == 0:
return
n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
idx_split = np.array_split(idx, n_packets)
header = bytes([1, 2]) # WARLS protocol header
for packet_indices in idx_split:
data = bytearray(header)
for i in packet_indices:
data.extend([i, *p[:, i]]) # Index and RGB values
self._sock.sendto(bytes(data), (self.wled_controller_ip, self.udp_port))
🤖 Prompt for AI Agents
In tools/udp_test.py around lines 21 to 29, n_packets can be zero when there are
no changed pixels which makes np.array_split(idx, n_packets) raise; fix this by
guarding the zero-changes path: check if the index array (idx) or num_pixels is
zero and return early before creating the header or splitting (e.g., if idx.size
== 0: return), otherwise compute n_packets and proceed; this prevents the
np.array_split error and avoids building/sending empty packets.


self._prev_pixels = np.copy(p)



################################## LED blink test ##################################
if __name__ == "__main__":
WLED_CONTROLLER_IP = "192.168.1.153"
NUM_PIXELS = 255 # Amount of LEDs on your strip
import time
wled = WledRealtimeClient(WLED_CONTROLLER_IP, NUM_PIXELS)
print('Starting LED blink test')
while True:
for i in range(NUM_PIXELS):
wled.pixels[1, i] = 255 if wled.pixels[1, i] == 0 else 0
wled.update()
time.sleep(.01)
6 changes: 3 additions & 3 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4116,7 +4116,7 @@ uint16_t mode_pacifica()

// Increment the four "color index start" counters, one for each wave layer.
// Each is incremented at a different speed, and the speeds vary over time.
uint16_t sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step, sCIStart4 = SEGENV.step >> 16;
unsigned sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step & 0xFFFF, sCIStart4 = (SEGENV.step >> 16);
uint32_t deltams = (FRAMETIME >> 2) + ((FRAMETIME * SEGMENT.speed) >> 7);
uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7);
strip.now = deltat;
Expand All @@ -4131,7 +4131,7 @@ uint16_t mode_pacifica()
sCIStart3 -= (deltams1 * beatsin88(501,5,7));
sCIStart4 -= (deltams2 * beatsin88(257,4,6));
SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2;
SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3;
SEGENV.step = (sCIStart4 << 16) | (sCIStart3 & 0xFFFF);

// Clear out the LED array to a dim background blue-green
//SEGMENT.fill(132618);
Expand Down Expand Up @@ -4162,7 +4162,7 @@ uint16_t mode_pacifica()
c.green = scale8(c.green, 200);
c |= CRGB( 2, 5, 7);

SEGMENT.setPixelColor(i, c.red, c.green, c.blue);
SEGMENT.setPixelColor(i, c);
}

strip.now = nowOld;
Expand Down
2 changes: 1 addition & 1 deletion wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@

// string temp buffer (now stored in stack locally) // WLEDMM ...which is actually not the greatest design choice on ESP32
#ifdef ESP8266
#define SETTINGS_STACK_BUF_SIZE 2048
#define SETTINGS_STACK_BUF_SIZE 2560
#else
#if !defined(USERMOD_AUDIOREACTIVE)
#define SETTINGS_STACK_BUF_SIZE 3834 // WLEDMM added 696+32 bytes of margin (was 3096)
Expand Down
3 changes: 2 additions & 1 deletion wled00/data/update.htm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
<h2>MoonMod WLED Software Update</h2>
<form method='POST' action='/update' id='uf' enctype='multipart/form-data' onsubmit="U()">
<span class="sip">##VERSION##</span><br> <!--WLEDMM: show bin name-->
Download the latest release: <a href="https://github.com/MoonModules/WLED/releases" target="_blank">
Download the latest binary:&nbsp;<a href="https://github.com/MoonModules/WLED/releases" target="_blank"
style="vertical-align: text-bottom; display: inline-flex;">
<img src="https://img.shields.io/github/release/MoonModules/WLED.svg?style=flat-square"></a><br>
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
<button type="submit">Update!</button><br>
Expand Down
5 changes: 2 additions & 3 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
// lx parser
#ifdef WLED_ENABLE_LOXONE
int lx = elem[F("lx")] | -1;
if (lx > 0) {
if (lx >= 0) {
parseLxJson(lx, id, false);
}
int ly = elem[F("ly")] | -1;
if (ly > 0) {
if (ly >= 0) {
parseLxJson(ly, id, true);
}
#endif
Expand Down Expand Up @@ -573,7 +573,6 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
ps = presetCycCurr;
if (root["win"].isNull() && getVal(root["ps"], &ps, 0, 0) && ps > 0 && ps < 251 && ps != currentPreset) {
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
presetCycCurr = ps;
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(ps, callMode); // async load from file system (only preset ID was specified)
if (iAmGroot) suspendStripService = false; // WLEDMM release lock
Expand Down
12 changes: 11 additions & 1 deletion wled00/presets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,21 @@ void initPresetsFile()
f.close();
}

#if 0 // not used in WLEDMM
bool applyPresetFromPlaylist(byte index)
{
DEBUG_PRINTF_P(PSTR("Request to apply preset: %d\n"), index);
presetToApply = presetCycCurr = index;
callModeToApply = CALL_MODE_DIRECT_CHANGE;
return true;
}
#endif

bool applyPreset(byte index, byte callMode)
{
DEBUG_PRINT(F("Request to apply preset: "));
DEBUG_PRINTLN(index);
presetToApply = index;
presetToApply = presetCycCurr = index;
callModeToApply = callMode;
return true;
}
Expand Down
39 changes: 26 additions & 13 deletions wled00/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,33 @@ bool isAsterisksOnly(const char* str, byte maxLen)
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t module)
{
unsigned long now = millis();

while (jsonBufferLock && millis()-now < 1100) delay(1); // wait for fraction for buffer lock

#if defined(ARDUINO_ARCH_ESP32)
// Use a recursive mutex type in case our task is the one holding the JSON buffer.
// This can happen during large JSON web transactions. In this case, we continue immediately
// and then will return out below if the lock is still held.
if (xSemaphoreTakeRecursive(jsonBufferLockMutex, 1100) == pdFALSE) return false; // timed out waiting
#elif defined(ARDUINO_ARCH_ESP8266)
// If we're in system context, delay() won't return control to the user context, so there's
// no point in waiting.
if (can_yield()) {
unsigned long now = millis();
while (jsonBufferLock && (millis()-now < 1100)) delay(1); // wait for fraction for buffer lock
}
#else
#error Unsupported task framework - fix requestJSONBufferLock
#endif
// If the lock is still held - by us, or by another task
if (jsonBufferLock) {
USER_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by "));
USER_PRINT(jsonBufferLock);
USER_PRINTLN(")");
return false; // waiting time-outed
DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), module, jsonBufferLock);
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
return false;
}

jsonBufferLock = module ? module : 255;
DEBUG_PRINT(F("JSON buffer locked. ("));
DEBUG_PRINT(jsonBufferLock);
DEBUG_PRINTLN(")");
DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock);
fileDoc = &doc; // used for applying presets (presets.cpp)
doc.clear();
return true;
Expand All @@ -238,11 +250,12 @@ bool requestJSONBufferLock(uint8_t module)

void releaseJSONBufferLock()
{
DEBUG_PRINT(F("JSON buffer released. ("));
DEBUG_PRINT(jsonBufferLock);
DEBUG_PRINTLN(")");
fileDoc = nullptr;
DEBUG_PRINTF_P(PSTR("JSON buffer released. (%d)\n"), jsonBufferLock);
jsonBufferLock = 0;
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
}


Expand Down
8 changes: 8 additions & 0 deletions wled00/wled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ static int wledmm_get_tcp_stacksize(void) {
}
#endif


extern "C" void usePWMFixedNMI(); // PWM bugfix for 8266

/*
* Main WLED class implementation. Mostly initialization and connection logic
*/
Expand Down Expand Up @@ -634,6 +637,11 @@ void WLED::setup()
#else
DEBUG_PRINTLN(F("PSRAM not used."));
#endif

#ifdef ESP8266
usePWMFixedNMI(); // link the 8266 NMI fix
#endif

#endif
#if defined(ARDUINO_ARCH_ESP32)
if (strncmp("ESP32-PICO", ESP.getChipModel(), 10) == 0) { // WLEDMM detect pico board at runtime
Expand Down
10 changes: 8 additions & 2 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,6 @@ WLED_GLOBAL int8_t irPin _INIT(IRPIN);
constexpr uint8_t hardwareTX = 1;
#endif

//WLED_GLOBAL byte presetToApply _INIT(0);

WLED_GLOBAL char ntpServerName[33] _INIT("0.wled.pool.ntp.org"); // NTP server to use

// WiFi CONFIG (all these can be changed via web UI, no need to set them here)
Expand Down Expand Up @@ -374,7 +372,11 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load
//if true, a segment per bus will be created on boot and LED settings save
//if false, only one segment spanning the total LEDs is created,
//but not on LED settings save if there is more than one segment currently
#ifdef WLED_AUTOSEGMENTS
WLED_GLOBAL bool autoSegments _INIT(true);
#else
WLED_GLOBAL bool autoSegments _INIT(false);
#endif
WLED_GLOBAL bool correctWB _INIT(false); // CCT color correction of RGB color
WLED_GLOBAL bool cctFromRgb _INIT(false); // CCT is calculated from RGB instead of using seg.cct
WLED_GLOBAL bool gammaCorrectCol _INIT(true ); // use gamma correction on colors // WLEDMM that's what you would think, but the code tells a different story.
Expand Down Expand Up @@ -822,6 +824,10 @@ WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> doc;
#endif // WLEDMM end
WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);

#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(xSemaphoreCreateRecursiveMutex());
#endif
Comment on lines +827 to +829
Copy link

@coderabbitai coderabbitai bot Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential initialization order issue with mutex creation.

Creating the recursive mutex with xSemaphoreCreateRecursiveMutex() at global scope (static initialization time) may occur before FreeRTOS is fully initialized on ESP32. This could lead to:

  • Mutex creation failure (returns NULL)
  • Undefined behavior if the scheduler isn't ready
  • Race conditions during early initialization

Consider initializing the mutex explicitly in setup() or using a lazy initialization pattern (create on first use with a check).

// Suggested approach 1: Initialize in setup()
#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(NULL);
#endif

// In setup() or initInterfaces():
void WLED::setup() {
  #if defined(ARDUINO_ARCH_ESP32)
  if (jsonBufferLockMutex == NULL) {
    jsonBufferLockMutex = xSemaphoreCreateRecursiveMutex();
  }
  #endif
  // ... rest of setup
}
// Suggested approach 2: Lazy initialization with helper
#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(NULL);

inline void ensureJsonMutexInit() {
  if (jsonBufferLockMutex == NULL) {
    jsonBufferLockMutex = xSemaphoreCreateRecursiveMutex();
  }
}
#endif
🤖 Prompt for AI Agents
In wled00/wled.h around lines 827-829, the global creation of the recursive
mutex via xSemaphoreCreateRecursiveMutex() can run before FreeRTOS is ready;
change the global initialization to NULL (do not call
xSemaphoreCreateRecursiveMutex() at static init time) and create the mutex
explicitly during WLED setup (or use a small helper for lazy init that checks
for NULL and calls xSemaphoreCreateRecursiveMutex() on first use), ensuring you
guard creation with a NULL check and store the returned handle back into
jsonBufferLockMutex.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the bunny is right here, calling xSemaphoreCreateRecursiveMutex(); before we have entered main() and setup() may or may not work.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@softhack007, thanks for confirming! You're absolutely right—calling xSemaphoreCreateRecursiveMutex() during static initialization is risky since FreeRTOS may not be ready yet.

The safest fix is to initialize the mutex handle to NULL at global scope and then create it explicitly in WLED::setup():

In wled00/wled.h (around line 827):

#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(NULL);
#endif

In wled00/wled.cpp (in the setup() function):

void WLED::setup() {
  #if defined(ARDUINO_ARCH_ESP32)
  if (jsonBufferLockMutex == NULL) {
    jsonBufferLockMutex = xSemaphoreCreateRecursiveMutex();
  }
  #endif
  // ... rest of setup code
}

This ensures the mutex is created after FreeRTOS is fully initialized and avoids any potential race conditions or initialization failures. You should also add a check in the lock/unlock functions (in wled00/util.cpp) to handle the case where mutex creation might fail, though that should be rare.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The things I have learned since I wrote that code!

In practice it works: this is because xSemaphoreCreateRecursiveMutex() happens to need only malloc() to be available, it doesn't interact with any of FreeRTOS's internal data structures. malloc() must be ready to use as part of the C runtime before any static initializers are run.

That said, better practice is to use a pre-allocated StaticSemaphore_t and xSemaphoreCreateRecursiveMutexStatic() in setup(); it can't fail if the memory is valid, so there's no need to consider the failure case at point of usage. (Also saves 8 bytes of heap overhead.)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


// enable additional debug output
//WLEDMM: switch between netdebug and serial
// cannot do this on -S2, due to buggy USBCDC serial driver: canUseSerial
Expand Down
5 changes: 4 additions & 1 deletion wled00/wled00.ino → wled00/wled_main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include <Arduino.h>
/*
* WLED Arduino IDE compatibility file.
* (this is the former wled00.ino)
*
* Where has everything gone?
*
* In April 2020, the project's structure underwent a major change.
* In April 2020, the project's structure underwent a major change.
* We now use the platformIO build system, and building WLED in Arduino IDE is not supported any more.
* Global variables are now found in file "wled.h"
* Global function declarations are found in "fcn_declare.h"
*
Expand Down