Skip to content

Commit 057a900

Browse files
committed
Refactor RDM device, discovery, and configstore
Large refactor and API cleanup across RDM, DMX node and configstore. - Introduces rdm::device::Base, rdm::device::InfoData and a new rdm discovery API (rdm_discovery.h) and state machine (renamed rdmdiscovery.h -> rdm_discovery_statemachine.h). Adds discovery state/management logic and TOD helpers. - Updates usages to the new rdm::device interfaces (firmware and prints), adjusts names for UID/SN access and device info structures. - Renames and relocates utility header (lib-dmxnode utils -> common/utils/utils_port.h) and changes namespace json -> common; removes static from template declarations. - Changes configuration store layout and API: replaces many set_list fields with flags, adds RdmDevice array helpers and fixes DmxNode port/name sizing (label -> port_name / kPortNameLength), and updates related uses across code (pixeldmx JSON, DmxNode API). - Miscellaneous: timestamp Makefile now exposes -D_TIME_STAMP_, removes firmware version id member and board/release constants, bumps copyright years, and minor cleanups (whitespace, header guards, include updates). These changes centralize RDM device information, standardize flag usage in the configuration store, and rename/align DMX node port naming for consistency. Review API changes when integrating dependent components.
1 parent 83caf7a commit 057a900

51 files changed

Lines changed: 1554 additions & 798 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

common/.settings/language.settings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
66
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
77
<provider copy-of="extension" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser"/>
8-
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="225383767331422835" id="org.eclipse.embedcdt.managedbuild.cross.arm.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Arm Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} ${cross_toolchain_flags} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
8+
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="1069578116264638190" id="org.eclipse.embedcdt.managedbuild.cross.arm.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Arm Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} ${cross_toolchain_flags} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
99
<language-scope id="org.eclipse.cdt.core.gcc"/>
1010
<language-scope id="org.eclipse.cdt.core.g++"/>
1111
</provider>
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,25 @@
2323
* THE SOFTWARE.
2424
*/
2525

26-
#ifndef DMXNODE_UTILS_H_
27-
#define DMXNODE_UTILS_H_
26+
#ifndef COMMON_UTILS_UTILS_PORT_H_
27+
#define COMMON_UTILS_UTILS_PORT_H_
2828

2929
#include <cstdint>
3030

31-
namespace json
31+
namespace common
3232
{
33-
template <class S> static void PortSet(uint32_t port_index, S s, uint16_t& n)
33+
template <class S> void PortSet(uint32_t port_index, S s, uint16_t& n)
3434
{
3535
uint16_t value = n; // Create a local copy
3636
value &= static_cast<uint16_t>(~(0x3 << (port_index * 2)));
3737
value |= static_cast<uint16_t>((static_cast<uint32_t>(s) & 0x3) << (port_index * 2));
3838
n = value; // Write back to the original field
3939
}
4040

41-
template <class S> static S PortGet(uint32_t port_index, uint16_t n)
41+
template <class S> S PortGet(uint32_t port_index, uint16_t n)
4242
{
4343
return static_cast<S>((n >> (port_index * 2)) & 0x3);
4444
}
45-
} // namespace json
45+
} // namespace common
4646

47-
#endif // DMXNODE_UTILS_H_
47+
#endif // COMMON_UTILS_UTILS_PORT_H_

common/make/Timestamp.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ $(info "Timestamp.mk")
22

33
ifneq ($(findstring _TIME_STAMP_YEAR_,$(DEFINES)), _TIME_STAMP_YEAR_)
44
DEFINES += \
5-
-D_TIME_STAMP_YEAR_=$(shell date +"%Y") \
5+
-D_TIME_STAMP_=$(shell date "+%s") \
6+
-D_TIME_STAMP_YEAR_=$(shell date +"%Y") \
67
-D_TIME_STAMP_MONTH_=$(shell date +"%m" | sed 's/^0*//') \
78
-D_TIME_STAMP_DAY_=$(shell date +"%d" | sed 's/^0*//')
89
endif

common/make/gd32/Validate.mk

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ifeq ($(FLAGS),)
88
endif
99

1010
ifneq ($(findstring _TIME_STAMP_YEAR_,$(FLAGS)),_TIME_STAMP_YEAR_)
11-
DEFINES+=-D_TIME_STAMP_YEAR_=$(shell date +"%Y") -D_TIME_STAMP_MONTH_=$(shell date +"%-m") -D_TIME_STAMP_DAY_=$(shell date +"%-d")
11+
include ../common/make/Timestamp.mk
1212
endif
1313

1414
ifneq (,$(findstring OUTPUT_DMX_SEND,$(FLAGS))$(findstring CONFIG_RDM,$(FLAGS))$(findstring RDM_CONTROLLER,$(FLAGS))$(findstring LTC,$(FLAGS)))
@@ -42,4 +42,10 @@ else
4242
DEFINES+=-DCONFIG_EMAC_HASH_MULTICAST_FILTER
4343
endif
4444

45+
ifeq ($(findstring RTL8201F,$(FLAGS)),RTL8201F)
46+
ifneq ($(findstring RTL8201F_LED1_LINK_ALL,$(FLAGS)),RTL8201F_LED1_LINK_ALL)
47+
DEFINES+=-DRTL8201F_LED1_LINK_ALL
48+
endif
49+
endif
50+
4551
$(info $$DEFINES [${DEFINES}])

common/scripts/fetch_configs.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Download config JSON files listed by /json/config/directory from a remote node,
4+
plus default endpoints: /json/version and /json/list.
5+
6+
This version resolves the host ONCE (e.g. .local via mDNS) and then uses the IP
7+
for all subsequent HTTP requests to avoid repeated resolver delays.
8+
9+
Usage:
10+
python3 fetch_configs.py <remote_node> <local_directory>
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import argparse
16+
import json
17+
import re
18+
import socket
19+
import sys
20+
import urllib.error
21+
import urllib.request
22+
from pathlib import Path
23+
from typing import Any, Dict, Tuple
24+
25+
26+
def normalize_host(host: str) -> str:
27+
host = host.strip()
28+
host = re.sub(r"^https?://", "", host) # remove scheme if provided
29+
host = host.split("/")[0] # remove any path
30+
host = host.rstrip(".") # handle ".local." style
31+
return host
32+
33+
34+
def resolve_host_once(host: str) -> Tuple[str, str]:
35+
"""
36+
Resolve host to an IP once.
37+
Returns (display_host, ip) where display_host is the original host for logging.
38+
"""
39+
try:
40+
# getaddrinfo gives us both IPv4/IPv6; prefer IPv4 for embedded nodes.
41+
infos = socket.getaddrinfo(host, 80, type=socket.SOCK_STREAM)
42+
except socket.gaierror as e:
43+
raise RuntimeError(f"DNS/mDNS lookup failed for {host}: {e}") from e
44+
45+
ipv4 = next((ai for ai in infos if ai[0] == socket.AF_INET), None)
46+
chosen = ipv4 or infos[0]
47+
ip = chosen[4][0]
48+
return host, ip
49+
50+
51+
def http_get_json(url: str, timeout: float = 10.0) -> Any:
52+
req = urllib.request.Request(
53+
url,
54+
headers={
55+
"Accept": "application/json",
56+
"User-Agent": "fetch-configs/1.2",
57+
},
58+
method="GET",
59+
)
60+
try:
61+
with urllib.request.urlopen(req, timeout=timeout) as resp:
62+
charset = resp.headers.get_content_charset() or "utf-8"
63+
data = resp.read().decode(charset, errors="replace")
64+
return json.loads(data)
65+
except urllib.error.HTTPError as e:
66+
body = ""
67+
try:
68+
body = e.read().decode("utf-8", errors="replace")
69+
except Exception:
70+
pass
71+
raise RuntimeError(f"HTTP {e.code} for {url}\n{body}".strip()) from e
72+
except urllib.error.URLError as e:
73+
raise RuntimeError(f"URL error for {url}: {e.reason}") from e
74+
except json.JSONDecodeError as e:
75+
raise RuntimeError(f"Invalid JSON from {url}: {e}") from e
76+
77+
78+
def save_json(out_dir: Path, filename: str, payload: Any) -> None:
79+
path = out_dir / filename
80+
with path.open("w", encoding="utf-8") as f:
81+
json.dump(payload, f, indent=4, sort_keys=True)
82+
f.write("\n")
83+
84+
85+
def fetch_and_save(ip: str, out_dir: Path, url_path: str, out_name: str) -> bool:
86+
url_path = url_path if url_path.startswith("/") else "/" + url_path
87+
url = f"http://{ip}{url_path}"
88+
try:
89+
payload = http_get_json(url)
90+
save_json(out_dir, out_name, payload)
91+
print(f"[OK] {url} -> {out_name}")
92+
return True
93+
except Exception as e:
94+
print(f"[FAIL] {url}: {e}", file=sys.stderr)
95+
return False
96+
97+
98+
def main() -> int:
99+
ap = argparse.ArgumentParser(description="Fetch remote JSON endpoints into a local directory.")
100+
ap.add_argument("remote_node", help="Hostname/IP (optionally with scheme), e.g. gigadevice_486149.local.")
101+
ap.add_argument("local_directory", help="Destination directory")
102+
args = ap.parse_args()
103+
104+
host = normalize_host(args.remote_node)
105+
out_dir = Path(args.local_directory).expanduser().resolve()
106+
out_dir.mkdir(parents=True, exist_ok=True)
107+
108+
print(f"Resolving: {host}")
109+
# Resolve once (fixes repeated 5s delays with .local/mDNS)
110+
display_host, ip = resolve_host_once(host)
111+
112+
print(f"Remote: {display_host} (resolved -> {ip})")
113+
print(f"Output: {out_dir}")
114+
print("")
115+
116+
ok = 0
117+
fail = 0
118+
119+
# 1) Default endpoints
120+
defaults = {
121+
"/json/version": "version.json",
122+
"/json/list": "list.json",
123+
}
124+
125+
for path, out_name in defaults.items():
126+
if fetch_and_save(ip, out_dir, path, out_name):
127+
ok += 1
128+
else:
129+
fail += 1
130+
131+
# 2) Directory + config endpoints
132+
directory_url = f"http://{ip}/json/config/directory"
133+
try:
134+
directory = http_get_json(directory_url)
135+
print(f"[OK] {directory_url} -> config_directory.json")
136+
save_json(out_dir, "config_directory.json", directory)
137+
ok += 1
138+
except Exception as e:
139+
print(f"[FAIL] {directory_url}: {e}", file=sys.stderr)
140+
fail += 1
141+
print("")
142+
print(f"Done. OK={ok} FAIL={fail}")
143+
return 2
144+
145+
files: Dict[str, Any] = {}
146+
if isinstance(directory, dict) and isinstance(directory.get("files"), dict):
147+
files = directory["files"]
148+
else:
149+
print(f"[FAIL] Unexpected directory JSON shape from {directory_url}", file=sys.stderr)
150+
print("")
151+
print(f"Done. OK={ok} FAIL={fail + 1}")
152+
return 2
153+
154+
for path_key in files.keys():
155+
remote_path = path_key.lstrip("/")
156+
url_path = f"/json/{remote_path}"
157+
basename = remote_path.split("/")[-1]
158+
out_name = f"{basename}.json"
159+
160+
if fetch_and_save(ip, out_dir, url_path, out_name):
161+
ok += 1
162+
else:
163+
fail += 1
164+
165+
print("")
166+
print(f"Done. OK={ok} FAIL={fail}")
167+
return 0 if fail == 0 else 2
168+
169+
170+
if __name__ == "__main__":
171+
raise SystemExit(main())

common/scripts/reboot.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
"""
3+
reboot.py
4+
5+
Default:
6+
Reboot once.
7+
8+
Optional:
9+
--forever / -f Loop forever
10+
11+
Usage:
12+
python3 reboot.py <ip_address>
13+
python3 reboot.py <ip_address> --forever
14+
"""
15+
16+
from __future__ import annotations
17+
18+
import sys
19+
import time
20+
import argparse
21+
from typing import Optional
22+
23+
from udp_send import send_and_maybe_recv
24+
25+
26+
LIST_CMD = b"?list#"
27+
REBOOT_CMD = b"?reboot##"
28+
29+
30+
def _reply_text(reply: Optional[bytes]) -> str:
31+
if not reply:
32+
return ""
33+
try:
34+
return reply.decode("utf-8", errors="replace").strip()
35+
except Exception:
36+
return repr(reply)
37+
38+
39+
def _color(s: str, code: str) -> str:
40+
return f"\033[{code}m{s}\033[0m"
41+
42+
43+
def wait_for_list(ip: str, *, timeout_sec: float = 1.0) -> str:
44+
while True:
45+
_sent, reply = send_and_maybe_recv(ip, LIST_CMD, timeout_sec=timeout_sec)
46+
text = _reply_text(reply)
47+
if text:
48+
return text
49+
time.sleep(0.1)
50+
51+
52+
def spinner_wait_for_online(ip: str, *, timeout_sec: float = 1.0) -> str:
53+
frames = ["|", "/", "-", "\\"]
54+
i = 0
55+
last_poll = 0.0
56+
poll_interval = 0.25
57+
58+
while True:
59+
now = time.monotonic()
60+
if now - last_poll >= poll_interval:
61+
last_poll = now
62+
_sent, reply = send_and_maybe_recv(ip, LIST_CMD, timeout_sec=timeout_sec)
63+
text = _reply_text(reply)
64+
if text:
65+
sys.stdout.write("\r" + " " * 60 + "\r")
66+
sys.stdout.flush()
67+
return text
68+
69+
sys.stdout.write(f"\rWaiting for reboot {frames[i % len(frames)]}")
70+
sys.stdout.flush()
71+
i += 1
72+
time.sleep(0.08)
73+
74+
75+
def do_reboot_cycle(ip: str) -> None:
76+
_sent, reply = send_and_maybe_recv(ip, LIST_CMD, timeout_sec=1.0)
77+
online = _reply_text(reply)
78+
79+
print(_color(f"[{online}]", "33")) # yellow
80+
81+
if not online:
82+
online = wait_for_list(ip)
83+
print(_color(f"[{online}]", "36")) # cyan
84+
85+
time.sleep(2)
86+
87+
send_and_maybe_recv(ip, REBOOT_CMD, timeout_sec=1.0)
88+
89+
online_after = spinner_wait_for_online(ip)
90+
print(_color(f"Back online: [{online_after}]", "32")) # green
91+
92+
93+
def main(argv: list[str]) -> int:
94+
parser = argparse.ArgumentParser(description="UDP reboot tool")
95+
parser.add_argument("ip", help="Target IP address")
96+
parser.add_argument(
97+
"-f",
98+
"--forever",
99+
action="store_true",
100+
help="Reboot forever (loop mode)",
101+
)
102+
103+
args = parser.parse_args(argv[1:])
104+
105+
if args.forever:
106+
while True:
107+
do_reboot_cycle(args.ip)
108+
else:
109+
do_reboot_cycle(args.ip)
110+
111+
return 0
112+
113+
114+
if __name__ == "__main__":
115+
raise SystemExit(main(sys.argv))
116+

gd32_dmx_usb_pro/.settings/language.settings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
66
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
77
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="false" name="CDT GCC Build Output Parser" parameter="([^/\\\\]*)((g?cc)|([gc]\+\+)|(clang))" prefer-non-shared="true"/>
8-
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="281858402852189984" id="org.eclipse.embedcdt.managedbuild.cross.arm.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Arm Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} ${cross_toolchain_flags} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
8+
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="1126052751785405339" id="org.eclipse.embedcdt.managedbuild.cross.arm.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Arm Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} ${cross_toolchain_flags} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
99
<language-scope id="org.eclipse.cdt.core.gcc"/>
1010
<language-scope id="org.eclipse.cdt.core.g++"/>
1111
</provider>

0 commit comments

Comments
 (0)