|
|
|
|
@@ -1,3 +1,5 @@
|
|
|
|
|
using S7NetCpuType = global::S7.Net.CpuType;
|
|
|
|
|
|
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
@@ -31,7 +33,7 @@ public enum S7Size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Parsed form of an S7 tag-address string. Produced by <see cref="S7AddressParser.Parse"/>.
|
|
|
|
|
/// Parsed form of an S7 tag-address string. Produced by <see cref="S7AddressParser.Parse(string)"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Area">Memory area (DB, M, I, Q, T, C).</param>
|
|
|
|
|
/// <param name="DbNumber">Data block number; only meaningful when <paramref name="Area"/> is <see cref="S7Area.DataBlock"/>.</param>
|
|
|
|
|
@@ -74,7 +76,29 @@ public static class S7AddressParser
|
|
|
|
|
/// the offending input echoed in the message so operators can correlate to the tag
|
|
|
|
|
/// config that produced the fault.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static S7ParsedAddress Parse(string address)
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// The CPU-agnostic overload rejects the <c>V</c> area letter; <c>V</c> is only
|
|
|
|
|
/// meaningful on S7-200 / S7-200 Smart / LOGO! where it maps to a fixed DB number
|
|
|
|
|
/// (DB1 by convention) — call <see cref="Parse(string, S7NetCpuType?)"/> with the
|
|
|
|
|
/// device's CPU family for V-memory tags.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public static S7ParsedAddress Parse(string address) => Parse(address, cpuType: null);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Parse an S7 address with knowledge of the device's CPU family. Required for the
|
|
|
|
|
/// <c>V</c> area letter (S7-200 / S7-200 Smart / LOGO! V-memory), which maps to
|
|
|
|
|
/// DataBlock DB1 on those families. On S7-300 / S7-400 / S7-1200 / S7-1500 the
|
|
|
|
|
/// <c>V</c> letter is rejected because it has no equivalent — those families use
|
|
|
|
|
/// explicit <c>DB{n}.DB...</c> addressing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// LOGO! firmware bands map V-memory to different underlying DB numbers in some
|
|
|
|
|
/// 0BA editions; the driver currently uses DB1 (the most common LOGO! 8 / 0BA8
|
|
|
|
|
/// mapping). If a future site ships a firmware band where VM lives in a different
|
|
|
|
|
/// DB, the mapping table in <see cref="VMemoryDbNumberFor"/> is the single point
|
|
|
|
|
/// to extend. Live LOGO! testing is out of scope for the initial PR.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public static S7ParsedAddress Parse(string address, S7NetCpuType? cpuType)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(address))
|
|
|
|
|
throw new FormatException("S7 address must not be empty");
|
|
|
|
|
@@ -97,21 +121,28 @@ public static class S7AddressParser
|
|
|
|
|
case 'Q': return ParseMIQ(S7Area.Output, rest, address);
|
|
|
|
|
case 'T': return ParseTimerOrCounter(S7Area.Timer, rest, address);
|
|
|
|
|
case 'C': return ParseTimerOrCounter(S7Area.Counter, rest, address);
|
|
|
|
|
case 'V': return ParseV(rest, address, cpuType);
|
|
|
|
|
default:
|
|
|
|
|
throw new FormatException($"S7 address '{address}' starts with unknown area '{areaChar}' (expected DB/M/I/Q/T/C)");
|
|
|
|
|
throw new FormatException($"S7 address '{address}' starts with unknown area '{areaChar}' (expected DB/M/I/Q/T/C/V)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Try-parse variant for callers that can't afford an exception on bad input (e.g.
|
|
|
|
|
/// config validation pages in the Admin UI). Returns <c>false</c> for any input that
|
|
|
|
|
/// would throw from <see cref="Parse"/>.
|
|
|
|
|
/// would throw from <see cref="Parse(string)"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool TryParse(string address, out S7ParsedAddress result)
|
|
|
|
|
=> TryParse(address, cpuType: null, out result);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Try-parse variant that accepts a CPU family for V-memory addressing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool TryParse(string address, S7NetCpuType? cpuType, out S7ParsedAddress result)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
result = Parse(address);
|
|
|
|
|
result = Parse(address, cpuType);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (FormatException)
|
|
|
|
|
@@ -206,6 +237,46 @@ public static class S7AddressParser
|
|
|
|
|
return new S7ParsedAddress(area, DbNumber: 0, size, byteOffset, bitOffset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Parse a <c>V</c>-area address (S7-200 / S7-200 Smart / LOGO! V-memory). Same width
|
|
|
|
|
/// suffixes as M/I/Q (<c>VB</c>, <c>VW</c>, <c>VD</c>, <c>V0.0</c>) but rewritten as
|
|
|
|
|
/// a DataBlock access so the rest of the driver — which speaks S7.Net's DB-centric
|
|
|
|
|
/// API — needs no special-casing downstream.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static S7ParsedAddress ParseV(string rest, string original, S7NetCpuType? cpuType)
|
|
|
|
|
{
|
|
|
|
|
var dbNumber = VMemoryDbNumberFor(cpuType, original);
|
|
|
|
|
// Reuse the M/I/Q grammar — V's size suffixes are identical (B/W/D/LD or .bit).
|
|
|
|
|
var parsed = ParseMIQ(S7Area.Memory, rest, original);
|
|
|
|
|
return parsed with { Area = S7Area.DataBlock, DbNumber = dbNumber };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Map a CPU family to the underlying DB number that backs V-memory. Returns DB1
|
|
|
|
|
/// for S7-200, S7-200 Smart, and LOGO! 0BA8 (the only LOGO! the S7.Net <c>CpuType</c>
|
|
|
|
|
/// enum surfaces). Throws for families that have no V-area concept.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static int VMemoryDbNumberFor(S7NetCpuType? cpuType, string original)
|
|
|
|
|
{
|
|
|
|
|
if (cpuType is null)
|
|
|
|
|
throw new FormatException(
|
|
|
|
|
$"S7 V-memory address '{original}' requires a CPU family (S7-200 / S7-200 Smart / LOGO!) — " +
|
|
|
|
|
"the CPU-agnostic Parse overload cannot resolve V-memory to a DB number");
|
|
|
|
|
|
|
|
|
|
return cpuType.Value switch
|
|
|
|
|
{
|
|
|
|
|
S7NetCpuType.S7200 => 1,
|
|
|
|
|
S7NetCpuType.S7200Smart => 1,
|
|
|
|
|
// LOGO! 8 / 0BA8 firmware bands typically expose VM as DB1 over S7comm. Older
|
|
|
|
|
// 0BA editions can differ; the mapping is centralised here for easy extension
|
|
|
|
|
// once a site provides a non-DB1 firmware band to test against.
|
|
|
|
|
S7NetCpuType.Logo0BA8 => 1,
|
|
|
|
|
_ => throw new FormatException(
|
|
|
|
|
$"S7 V-memory address '{original}' is only valid on S7-200 / S7-200 Smart / LOGO! " +
|
|
|
|
|
$"(got CpuType={cpuType.Value}); use explicit DB{{n}}.DB... addressing on this family"),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static S7ParsedAddress ParseTimerOrCounter(S7Area area, string rest, string original)
|
|
|
|
|
{
|
|
|
|
|
if (rest.Length == 0)
|
|
|
|
|
|