/// /// Copyright (c) 2021 Sensus Slovensko a.s. /// using System; using System.Diagnostics; using log4net; using Dirichlet.Numerics; namespace TBF.Rig.ControlBoard.Uni { public static class OutputMessage { private static readonly ILog log = LogManager.GetLogger(typeof(OutputMessage)); public const int PayloadLen = 38; public const byte MessageCode = (byte)' '; static Action arguments = new Action(); /// static void FetchArguments(Action combinedAction) { foreach (var aId in (ActionID[])Enum.GetValues(typeof(ActionID))) { if ((combinedAction.ActionId & aId) != 0) arguments.SetArgs(combinedAction, aId); } } public static byte[] GetMessage(Action action, short config, ulong bitsToInvert) { FetchArguments(action); byte[] message = new byte[PayloadLen + 5]; /// headed 3 bytes, checksum 2 bytes /// Header + message code (4 bytes) message[0] = Const.STX; message[1] = 1; /// Control board addres message[2] = (byte)PayloadLen; /// Payload length message[3] = MessageCode; /// Command message[4] = GetCommand(action); message[5] = action.IsStart() ? GetStartParams(action) : action.IsStop() ? GetStopParams(action) : (byte)0; message[6] = (arguments.DivId <= 0) ? (byte)0 : (byte)(arguments.DivId - 1); int totalPulsesCount = (arguments.TotalPulsesCount < 0) ? 0 : arguments.TotalPulsesCount; message[7] = (byte)(totalPulsesCount & 0xFF); message[8] = (byte)((totalPulsesCount >> 8) & 0xFF); message[9] = (byte)((totalPulsesCount >> 16) & 0xFF); int massPulsesCount = (arguments.MassPulsesCount < 0) ? 0 : arguments.MassPulsesCount; message[10] = (byte)(massPulsesCount & 0xFF); message[11] = (byte)((massPulsesCount >> 8) & 0xFF); message[12] = (byte)((massPulsesCount >> 16) & 0xFF); message[13] = 0; message[14] = (byte)(config & 0xff); message[15] = (byte)((config >> 8) & 0xff); /// Route ulong routeOut = arguments.Route ^ bitsToInvert; message[16] = ((action.ActionId & ActionID.ChangeRoute) != 0) ? (byte)1 : (byte)0; /// Route change flag message[17] = (byte)(routeOut & 0xFF); message[18] = (byte)((routeOut >> 8) & 0xFF); message[19] = (byte)((routeOut >> 16) & 0xFF); message[20] = (byte)((routeOut >> 24) & 0xFF); message[21] = (byte)((routeOut >> 32) & 0xFF); message[22] = (byte)((routeOut >> 40) & 0xFF); message[23] = (byte)((routeOut >> 48) & 0xFF); message[24] = (byte)((routeOut >> 56) & 0xFF); /// DA2 message[25] = 0; message[26] = 0; /// Regulation valve message[27] = (action.RegVId < 0) ? (byte)0 : (byte)(action.RegVId | (int)action.RegVMode); message[28] = (byte)(action.RVMovePar1 & 0xFF); message[29] = (byte)((action.RVMovePar1 >> 8) & 0xFF); message[30] = (byte)(action.RVMovePar2 & 0xFF); message[31] = (byte)((action.RVMovePar2 >> 8) & 0xFF); message[32] = GetRVStopParam(action); /// DA1 message[33] = 0; /// PrudParL message[34] = 0; /// PrudParH message[35] = (byte)Math.Max(1, Math.Min(255, arguments.PID)); /// PIDRV message[36] = 0; // (byte)Math.Max(4, Math.Min(255, action.TolerRV)); /// TolerRV (4..20 mA reg.valve only) message[37] = (byte)Math.Max(0, Math.Min(255, arguments.StabTime)); /// StabRV message[38] = (byte)((arguments.DivThreshold >> 2) & 0xFF); message[39] = 0; message[40] = 0; Debug.Assert(40 == PayloadLen + 2); /// Checksum (2 bytes) int checksum = 0; for (int i = 1; i < PayloadLen + 3; i++) checksum += message[i]; message[PayloadLen + 3] = (byte)(checksum & 0xFF); message[PayloadLen + 4] = (byte)((checksum >> 8) & 0xFF); return message; } static byte GetCommand(Action action) { if (action.StartStop == Command.DelayStartOrStop) { return (byte)Command.None; } else if (action.StartStop == Command.Start && action.IsProlonged) { return (byte)(Command.Start | Command.ProlongedFlag); } else { return (byte)action.StartStop; } } static byte GetStartParams(Action action) { int param = action.FlowMId & (int)StartParam.EtalonMask; if (action.IsDivUsed) param |= (int)StartParam.DiverterStart; if (action.IsSyncMethod) param |= (int)StartParam.SynchroMethod; if (action.IsStartStop) param |= (int)StartParam.StartStopMethod; return (byte)param; } static byte GetStopParams(Action action) { return (byte)StopDevs.All; } static byte GetRVStopParam(Action action) { if (action.IsStop()) return (byte)0x20; if ((action.ActionId & ActionID.RegVlvStop) != 0) return (byte)0x20; return (byte)0; } } public enum Command : byte { None = 0, /// Request data only Start = 1, Stop = 2, GetDiverterTransitionData = 4, GetScopeAnalyzerData = 7, ResetScopeAnalyzer = 8, GetSwitchCounterData = 9, /// ProlongedFlag = 0x10, /// Not a command of its own, a valid command is (byte)(Command.Start | Command.ProlongedFlag) DelayStartOrStop = 255, /// Prevents other start or stop command, results in Command.None when creating a message } public enum StartParam : byte { EtalonMask = 0x07, DiverterStart = 0x08, SynchroMethod = 0x10, DirectGateStart = 0x20, /// ??? StartStopMethod = 0x40, CalibrationMode = 0x80, } public enum StopDevs : byte { None = 0, Reference = 0x01, /// bit 0 Diverter = 0x02, /// bit 1 GatePulse = 0x08, /// bit 3 TimeMeasurement = 0x10, /// bit 4 BypassDevCoupled2Div = 0x20, /// bit 5 RegValveRegulation = 0x80, /// bit 7 All = 0x9B, } public enum ValveChgFlag : byte { DoNotChange = 0, Change = 1, SendPressureMeterAddresses = 2, } public enum RegValveMode : byte { None = 0, TargetFrequency = 0x10, /// Low and high frequency limits (0..2000Hz,0..2200Hz) are specified TargetPosition = 0x20, /// Low and high position (=DAC value) limits (0..100%) are specified Stop = 0x30, /// Stop regulation PulseWidth = 0x40, /// Pulse width 0.05 .. 2 sec. ... opening and closing (opening=0x40, closing=0xC0) NegPulseWidth = 0x80, /// Pulse width 0.05 .. 2 sec. ... closing (closing=0xC0) } public enum RegValveState : byte { Idle = 0, PwOrFreqRegul = 2, DacValueRegul = 3, } }