Skip to content
Merged
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
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,21 @@ resources/strucpp/
playwright-report
/blob-report/
/playwright/.cache/

# ---------------------------------------------------------------------------
# JWPLC local analysis / search dumps
# ---------------------------------------------------------------------------
jwplc-*-search.txt
jwplc-*search*.txt
*-jwplc-search.txt
*jwplc*search*.txt

# Local downloaded CI/log artifacts
logs_*.zip
logs_*/
*.log.zip

# Temporary local notes generated during debugging
*_search.txt
*_debug_search.txt
*_local_search.txt
47 changes: 38 additions & 9 deletions resources/sources/Baremetal/Baremetal.ino
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@
#include "defines.h"
#include "arduino_runtime_glue.h"

#if defined(JWPLC_BASIC)

#include <JWPLC_RS485.h>

#ifndef JWPLC_RS485_RX_PIN
#define JWPLC_RS485_RX_PIN 16
#endif

#ifndef JWPLC_RS485_TX_PIN
#define JWPLC_RS485_TX_PIN 17
#endif
#endif

#ifdef MODBUS_ENABLED
#include "ModbusSlave.h"
#endif
Expand Down Expand Up @@ -148,11 +161,21 @@ void setup()
{
if (pinMask_AOUT[i] == MBSERIAL_TXPIN) pinMask_AOUT[i] = 255;
}
MBSERIAL_IFACE.begin(MBSERIAL_BAUD);
mbconfig_serial_iface(&MBSERIAL_IFACE, MBSERIAL_BAUD, MBSERIAL_TXPIN);
#if defined(JWPLC_BASIC) && defined(MBSERIAL_IFACE_IS_SERIAL2)
JWPLC_RS485.begin(MBSERIAL_BAUD);
mbconfig_serial_iface(&JWPLC_RS485, MBSERIAL_BAUD, MBSERIAL_TXPIN);
#else
MBSERIAL_IFACE.begin(MBSERIAL_BAUD);
mbconfig_serial_iface(&MBSERIAL_IFACE, MBSERIAL_BAUD, MBSERIAL_TXPIN);
#endif
#else
MBSERIAL_IFACE.begin(MBSERIAL_BAUD);
mbconfig_serial_iface(&MBSERIAL_IFACE, MBSERIAL_BAUD, -1);
#if defined(JWPLC_BASIC) && defined(MBSERIAL_IFACE_IS_SERIAL2)
JWPLC_RS485.begin(MBSERIAL_BAUD);
mbconfig_serial_iface(&JWPLC_RS485, MBSERIAL_BAUD, -1);
#else
MBSERIAL_IFACE.begin(MBSERIAL_BAUD);
mbconfig_serial_iface(&MBSERIAL_IFACE, MBSERIAL_BAUD, -1);
#endif
#endif
modbus.slaveid = MBSERIAL_SLAVE;
#endif
Expand All @@ -164,16 +187,22 @@ void setup()
uint8_t gateway[] = { MBTCP_GATEWAY };
uint8_t subnet[] = { MBTCP_SUBNET };

#if defined(JWPLC_BASIC)
uint8_t *mbtcpMac = (sizeof(mac) / sizeof(uint8_t) >= 6) ? mac : NULL;
#else
uint8_t *mbtcpMac = mac;
#endif

if (sizeof(ip)/sizeof(uint8_t) < 4)
mbconfig_ethernet_iface(mac, NULL, NULL, NULL, NULL);
mbconfig_ethernet_iface(mbtcpMac, NULL, NULL, NULL, NULL);
else if (sizeof(dns)/sizeof(uint8_t) < 4)
mbconfig_ethernet_iface(mac, ip, NULL, NULL, NULL);
mbconfig_ethernet_iface(mbtcpMac, ip, NULL, NULL, NULL);
else if (sizeof(gateway)/sizeof(uint8_t) < 4)
mbconfig_ethernet_iface(mac, ip, dns, NULL, NULL);
mbconfig_ethernet_iface(mbtcpMac, ip, dns, NULL, NULL);
else if (sizeof(subnet)/sizeof(uint8_t) < 4)
mbconfig_ethernet_iface(mac, ip, dns, gateway, NULL);
mbconfig_ethernet_iface(mbtcpMac, ip, dns, gateway, NULL);
else
mbconfig_ethernet_iface(mac, ip, dns, gateway, subnet);
mbconfig_ethernet_iface(mbtcpMac, ip, dns, gateway, subnet);
#endif

init_mbregs(MAX_ANALOG_OUTPUT + MAX_MEMORY_WORD, MAX_MEMORY_DWORD, MAX_MEMORY_LWORD, MAX_DIGITAL_OUTPUT, MAX_ANALOG_INPUT, MAX_DIGITAL_INPUT);
Expand Down
132 changes: 118 additions & 14 deletions resources/sources/Baremetal/ModbusSlave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Copyright (C) 2022 OpenPLC - Thiago Alves
// of the precompiled OpenPLCUserLib archive built with -std=gnu++17.
#include "arduino_runtime_glue.h"

#if defined(JWPLC_BASIC)
#include <JWPLC_Ethernet.h>
#endif

//Global Modbus vars
struct MBinfo modbus;
uint8_t mb_frame[MAX_MB_FRAME];
Expand All @@ -22,9 +26,12 @@ uint16_t mb_t15; // inter character time out
uint16_t mb_t35; // frame delay

#ifdef MBTCP_ETHERNET
#ifdef BOARD_ESP32
#if defined(JWPLC_BASIC)
EthernetServer mb_server(502);
static bool jwplc_mbtcp_ready = false;
#elif defined(BOARD_ESP32)
WiFiServer mb_server(502);
WiFiClient mb_serverClients[MAX_SRV_CLIENTS];
WiFiClient mb_serverClients[MAX_SRV_CLIENTS];
#else
EthernetServer mb_server(502);
#endif
Expand Down Expand Up @@ -167,8 +174,80 @@ void mbconfig_serial_iface(Stream* port, long baud, int txPin)
void mbconfig_ethernet_iface(uint8_t *mac, uint8_t *ip, uint8_t *dns, uint8_t *gateway, uint8_t *subnet)
{
#ifdef MBTCP_ETHERNET
#ifdef BOARD_ESP32

#if defined(JWPLC_BASIC)
// JWPLC Basic usa W5500 por SPI compartido. No usar ETH.begin() ni
// Ethernet.begin() directo: JWPLC_Ethernet inicializa CS, SPI,
// timeouts, DHCP/IP estatica, link y estado del W5500.
jwplc_mbtcp_ready = false;

bool ethOk = false;

#if defined(JWPLC_MBTCP_DEBUG)
Serial.begin(115200);
delay(300);
Serial.println();
Serial.println("[JWPLC][MBTCP] Starting Ethernet...");
#endif

if (ip == NULL)
{
#if defined(JWPLC_MBTCP_DEBUG)
Serial.println("[JWPLC][MBTCP] Mode: DHCP");
#endif
JWPLC_Ethernet.useDHCP();

// Si el MAC viene vacío desde el VPP, usa uno local por defecto.
uint8_t defaultMac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01 };
ethOk = JWPLC_Ethernet.begin(mac != NULL ? mac : defaultMac);
}
else
{
#if defined(JWPLC_MBTCP_DEBUG)
Serial.println("[JWPLC][MBTCP] Mode: static IP");
#endif
IPAddress localIp(ip);
IPAddress dnsIp = (dns != NULL) ? IPAddress(dns) : localIp;
IPAddress gatewayIp = (gateway != NULL) ? IPAddress(gateway) : localIp;
IPAddress subnetIp = (subnet != NULL) ? IPAddress(subnet) : IPAddress(255, 255, 255, 0);

uint8_t defaultMac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01 };
JWPLC_Ethernet.setMac(mac != NULL ? mac : defaultMac);
ethOk = JWPLC_Ethernet.begin(localIp, dnsIp, gatewayIp, subnetIp);
}

IPAddress assignedIp = JWPLC_Ethernet.localIP();
bool hasIp = (assignedIp != IPAddress(0, 0, 0, 0));

#if defined(JWPLC_MBTCP_DEBUG)
Serial.print("[JWPLC][MBTCP] ethOk: ");
Serial.println(ethOk ? "true" : "false");
Serial.print("[JWPLC][MBTCP] IP: ");
Serial.println(assignedIp);
Serial.print("[JWPLC][MBTCP] Status: ");
Serial.println(JWPLC_Ethernet.statusString());
#endif

if (ethOk || hasIp)
{
mb_server.begin();
jwplc_mbtcp_ready = true;
#if defined(JWPLC_MBTCP_DEBUG)
Serial.println("[JWPLC][MBTCP] Server started on port 502");
#endif
}
else
{
jwplc_mbtcp_ready = false;
#if defined(JWPLC_MBTCP_DEBUG)
Serial.println("[JWPLC][MBTCP] Server NOT started");
#endif
}

return;

#elif defined(BOARD_ESP32)

ETH.begin();

if (ip != NULL && subnet != NULL && gateway != NULL)
Expand Down Expand Up @@ -196,6 +275,7 @@ void mbconfig_ethernet_iface(uint8_t *mac, uint8_t *ip, uint8_t *dns, uint8_t *g
// }

#endif

#ifdef MBTCP_WIFI
#if defined(BOARD_ESP8266) || defined(BOARD_ESP32)
if (ip != NULL && gateway != NULL && subnet != NULL && dns != NULL)
Expand Down Expand Up @@ -239,23 +319,47 @@ void mbconfig_ethernet_iface(uint8_t *mac, uint8_t *ip, uint8_t *dns, uint8_t *g

void mbtask()
{
#ifdef MBTCP
#ifdef MBTCP
#if defined(JWPLC_BASIC) && defined(MBTCP_ETHERNET)
if (jwplc_mbtcp_ready)
{
handle_tcp();
#endif
#ifdef MBSERIAL
handle_serial();
#endif

static uint32_t lastMaintainMs = 0;
uint32_t nowMs = millis();
if ((uint32_t)(nowMs - lastMaintainMs) >= 1000UL)
{
JWPLC_Ethernet.maintain();
lastMaintainMs = nowMs;
}
}

// Cede tiempo al scheduler ESP32 para no afectar TFT, E/S, RTC, Ethernet tick, etc.
delay(1);
#else
handle_tcp();
#endif
#endif

#ifdef MBSERIAL
handle_serial();
#endif
}

#ifdef MBTCP
void handle_tcp()
{
#ifdef MBTCP_ETHERNET
#ifdef BOARD_ESP32
WiFiClient client = mb_server.available();
#else
EthernetClient client = mb_server.available();
#endif
#if defined(JWPLC_BASIC)
if (!jwplc_mbtcp_ready)
return;

EthernetClient client = mb_server.available();
#elif defined(BOARD_ESP32)
WiFiClient client = mb_server.available();
#else
EthernetClient client = mb_server.available();
#endif
#endif

#if defined(MBTCP_WIFI) && !defined(BOARD_ESP8266) && !defined(BOARD_ESP32)
Expand All @@ -264,7 +368,7 @@ void handle_tcp()

//ESP and Portenta boards have a slightly different implementation of the WiFi/Ethernet API - therefore their specific
//code lies below
#if (defined(BOARD_ESP8266) || defined(BOARD_ESP32) || defined(BOARD_PORTENTA)) || defined(BOARD_PICOW) && (defined(MBTCP_WIFI) || defined(MBTCP_ETHERNET))
#if !defined(JWPLC_BASIC) && ((defined(BOARD_ESP8266) || defined(BOARD_ESP32) || defined(BOARD_PORTENTA)) || (defined(BOARD_PICOW) && (defined(MBTCP_WIFI) || defined(MBTCP_ETHERNET))))


#if defined(BOARD_PORTENTA) || defined(BOARD_PICOW) || (defined(BOARD_ESP32) && defined(MBTCP_ETHERNET))
Expand Down
9 changes: 7 additions & 2 deletions resources/sources/Baremetal/ModbusSlave.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ Copyright (C) 2022 OpenPLC - Thiago Alves
//Platform specific defines and includes
#ifdef MBTCP_ETHERNET
#include <SPI.h>
#ifdef BOARD_ESP32
#if defined(JWPLC_BASIC)
// JWPLC Basic usa W5500 por SPI. No usar ETH.h / ETH.begin().
#include <Ethernet.h>
#elif defined(BOARD_ESP32)
// I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
#define ETH_PHY_ADDR 0 // DEFAULT VALUE IS 0 YOU CAN OMIT IT
// Type of the Ethernet PHY (LAN8720 or TLK110)
Expand Down Expand Up @@ -131,7 +134,9 @@ extern uint16_t mb_t15; // inter character time out
extern uint16_t mb_t35; // frame delay

#ifdef MBTCP_ETHERNET
#ifdef BOARD_ESP32
#if defined(JWPLC_BASIC)
extern EthernetServer mb_server;
#elif defined(BOARD_ESP32)
extern WiFiServer mb_server;
#else
extern EthernetServer mb_server;
Expand Down
9 changes: 7 additions & 2 deletions src/backend/shared/compile/steps/modbus-defines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ const RTU_DEFAULTS = {

const TCP_DEFAULTS = {
tcp_interface: 'Ethernet' as const,
}
tcp_mac_address: 'DE:AD:BE:EF:00:01',
} as const

/**
* Build the `//Comms Configuration` block. Returns an empty string when
Expand Down Expand Up @@ -126,6 +127,9 @@ export function generateModbusDefines(state: VppModbusScreenState): string {
const baud = rtu.rtu_baud_rate ?? RTU_DEFAULTS.rtu_baud_rate
const slave = typeof rtu.rtu_slave_id === 'number' ? rtu.rtu_slave_id : RTU_DEFAULTS.rtu_slave_id
lines.push(`#define MBSERIAL_IFACE ${iface}`)
if (iface === 'Serial2') {
lines.push('#define MBSERIAL_IFACE_IS_SERIAL2')
}
lines.push(`#define MBSERIAL_BAUD ${baud}`)
lines.push(`#define MBSERIAL_SLAVE ${slave}`)
if (rtu.enable_rs485_en_pin === true && rtu.rtu_rs485_en_pin) {
Expand All @@ -146,7 +150,8 @@ export function generateModbusDefines(state: VppModbusScreenState): string {
// falls back to the DHCP/NULL path. Wi-Fi mode ignores these args
// inside `mbconfig_ethernet_iface` (see `ModbusSlave.cpp:199-225`),
// so the placeholder values are harmless there too.
const macLiteral = tcp.tcp_mac_address ? formatMacForDefine(tcp.tcp_mac_address) : '0'
const rawMac = (tcp.tcp_mac_address ?? TCP_DEFAULTS.tcp_mac_address).trim()
const macLiteral = rawMac ? formatMacForDefine(rawMac) : formatMacForDefine(TCP_DEFAULTS.tcp_mac_address)
lines.push(`#define MBTCP_MAC ${macLiteral}`)

const dhcpOn = tcp.enable_dhcp === true
Expand Down
23 changes: 19 additions & 4 deletions src/backend/shared/hardware/debug-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,31 @@ function lookupRef(path: string, state: DebugResolverState): unknown {
return cursor
}

function isBlank(value: unknown): boolean {
return value === undefined || value === null || (typeof value === 'string' && value.trim() === '')
}

function evaluateRef(ref: DebugRef, state: DebugResolverState): unknown {
const raw = lookupRef(ref.$ref, state)
const resolved = raw === undefined ? ref.default : raw
if (resolved === undefined) return undefined
let resolved = lookupRef(ref.$ref, state)

if (isBlank(resolved) && ref.fallback) {
resolved = lookupRef(ref.fallback, state)
}

if (isBlank(resolved) && ref.default !== undefined) {
resolved = ref.default
}

if (isBlank(resolved)) return undefined

if (ref.as === 'number') {
const n = typeof resolved === 'number' ? resolved : Number(resolved)
return Number.isFinite(n) ? n : undefined
}
if (ref.as === 'boolean') return Boolean(resolved)

if (ref.as === 'boolean') return resolved === true || resolved === 'true'
if (ref.as === 'string') return String(resolved)

return resolved
}

Expand Down
Loading
Loading