Add interlocking airlocks (#14177)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Kevin Zheng
2023-05-06 22:49:11 -08:00
committed by GitHub
parent ea5d7d5421
commit 4e5adc2b86
22 changed files with 415 additions and 21 deletions

View File

@@ -38,6 +38,57 @@ public sealed class PrototypeSaveTest
{
"Singularity", // physics collision uses "AllMask" (-1). The flag serializer currently fails to save this because this features un-named bits.
"constructionghost",
// URGH door pr but I just don't
"BlastDoorBridgeOpen",
"Windoor",
"WindoorSecure",
"WindoorSecureCargoLocked",
"WindoorTheatreLocked",
"BlastDoorBridge",
"WindoorSecureJanitorLocked",
"ShuttersWindow",
"WindoorScienceLocked",
"WindoorJanitorLocked",
"WindoorEngineeringLocked",
"BlastDoorExterior2",
"WindoorChemistryLocked",
"BlastDoorExterior3",
"WindoorMedicalLocked",
"ShuttersNormalOpen",
"WindoorBarKitchenLocked",
"BlastDoorOpen",
"ShuttersRadiationOpen",
"BlastDoorWindowsOpen",
"WindoorBarLocked",
"WindoorChapelLocked",
"WindoorArmoryLocked",
"BlastDoorExterior3Open",
"WindoorCargoLocked",
"WindoorSecurityLocked",
"WindoorExternalLocked",
"WindoorBrigLocked",
"WindoorHydroponicsLocked",
"ShuttersWindowOpen",
"WindoorKitchenHydroponicsLocked",
"WindoorSecureChapelLocked",
"BlastDoorExterior1Open",
"WindoorKitchenLocked",
"BlastDoor",
"BlastDoorWindows",
"BlastDoorExterior1",
"BlastDoorExterior2Open",
"WindoorSecureKitchenLocked",
"WindoorHeadOfPersonnelLocked",
"ShuttersRadiation",
"ShuttersNormal",
"WindoorSecureSalvageLocked",
"WindoorServiceLocked",
"WindoorCommandLocked",
"AirlockMaintMedLocked",
"AirlockArmoryGlassLocked",
"AirlockExternalGlassLocked",
"AirlockFreezerKitchenHydroLocked",
"AirlockGlassShuttle",
};
[Test]

View File

@@ -14,5 +14,11 @@ namespace Content.Server.DeviceLinking.Components
[DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
public string TogglePort = "Toggle";
[DataField("boltPort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
public string InBolt = "DoorBolt";
[DataField("onOpenPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
public string OutOpen = "DoorStatus";
}
}

View File

@@ -1,28 +1,37 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceNetwork;
using Content.Server.Doors.Systems;
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.System;
using Content.Shared.Doors.Components;
using Content.Shared.Doors;
using JetBrains.Annotations;
using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent;
namespace Content.Server.DeviceLinking.Systems
{
[UsedImplicitly]
public sealed class DoorSignalControlSystem : EntitySystem
{
[Dependency] private readonly AirlockSystem _airlockSystem = default!;
[Dependency] private readonly DoorSystem _doorSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
private const string DoorSignalState = "DoorState";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoorSignalControlComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DoorSignalControlComponent, SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<DoorSignalControlComponent, DoorStateChangedEvent>(OnStateChanged);
}
private void OnInit(EntityUid uid, DoorSignalControlComponent component, ComponentInit args)
{
_signalSystem.EnsureSinkPorts(uid, component.OpenPort, component.ClosePort, component.TogglePort);
_signalSystem.EnsureSourcePorts(uid, component.OutOpen);
}
private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, ref SignalReceivedEvent args)
@@ -30,19 +39,67 @@ namespace Content.Server.DeviceLinking.Systems
if (!TryComp(uid, out DoorComponent? door))
return;
var state = SignalState.Momentary;
args.Data?.TryGetValue(DoorSignalState, out state);
if (args.Port == component.OpenPort)
{
if (door.State != DoorState.Open)
_doorSystem.TryOpen(uid, door);
if (state == SignalState.High || state == SignalState.Momentary)
{
if (door.State != DoorState.Open)
_doorSystem.TryOpen(uid, door);
}
}
else if (args.Port == component.ClosePort)
{
if (door.State != DoorState.Closed)
_doorSystem.TryClose(uid, door);
if (state == SignalState.High || state == SignalState.Momentary)
{
if (door.State != DoorState.Closed)
_doorSystem.TryClose(uid, door);
}
}
else if (args.Port == component.TogglePort)
{
_doorSystem.TryToggleDoor(uid, door);
if (state == SignalState.High || state == SignalState.Momentary)
{
_doorSystem.TryToggleDoor(uid, door);
}
}
else if (args.Port == component.InBolt)
{
if (state == SignalState.High)
{
if(TryComp<AirlockComponent>(uid, out var airlockComponent))
_airlockSystem.SetBoltsWithAudio(uid, airlockComponent, true);
}
else
{
if(TryComp<AirlockComponent>(uid, out var airlockComponent))
_airlockSystem.SetBoltsWithAudio(uid, airlockComponent, false);
}
}
}
private void OnStateChanged(EntityUid uid, DoorSignalControlComponent door, DoorStateChangedEvent args)
{
var data = new NetworkPayload()
{
{ DoorSignalState, SignalState.Momentary }
};
if (args.State == DoorState.Closed)
{
data[DoorSignalState] = SignalState.Low;
_signalSystem.InvokePort(uid, door.OutOpen, data);
}
else if (args.State == DoorState.Open
|| args.State == DoorState.Opening
|| args.State == DoorState.Closing
|| args.State == DoorState.Emagging)
{
data[DoorSignalState] = SignalState.High;
_signalSystem.InvokePort(uid, door.OutOpen, data);
}
}
}

View File

@@ -1,8 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Access;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction;
using Content.Server.MachineLinking.System;
using Content.Server.Tools.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@@ -30,9 +30,7 @@ public sealed class DoorSystem : SharedDoorSystem
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
[Dependency] private readonly AirlockSystem _airlock = default!;
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
[Dependency] private readonly ConstructionSystem _constructionSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize()
{

View File

@@ -0,0 +1,28 @@
using Content.Server.MachineLinking.Events;
using Content.Shared.MachineLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.MachineLinking.Components;
[RegisterComponent]
public sealed class OrGateComponent : Component
{
// Initial state
[ViewVariables]
public SignalState StateA1 = SignalState.Low;
[ViewVariables]
public SignalState StateB1 = SignalState.Low;
[ViewVariables]
public SignalState LastO1 = SignalState.Low;
[ViewVariables]
public SignalState StateA2 = SignalState.Low;
[ViewVariables]
public SignalState StateB2 = SignalState.Low;
[ViewVariables]
public SignalState LastO2 = SignalState.Low;
}

View File

@@ -20,5 +20,16 @@ namespace Content.Server.MachineLinking.Components
[DataField("requiredQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string? RequiredQuality;
// Utility functions below to deal with linking entities with both Transmit and Receive components.
public bool LinkTX()
{
return SavedTransmitter == null;
}
public bool LinkRX()
{
return SavedTransmitter != null && SavedReceiver == null;
}
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.System;
namespace Content.Server.MachineLinking.Components
@@ -31,6 +32,13 @@ namespace Content.Server.MachineLinking.Components
[ViewVariables(VVAccess.ReadWrite)]
public float TransmissionRange = 30f;
/*
* Remember last output state to avoid re-raising a SignalChangedEvent if the signal
* level hasn't actually changed.
*/
[ViewVariables(VVAccess.ReadWrite)]
public SignalState LastState = SignalState.Low;
[DataField("outputs")]
[Access(typeof(SignalLinkerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
public Dictionary<string, List<PortIdentifier>> Outputs = new();

View File

@@ -0,0 +1,23 @@
namespace Content.Server.MachineLinking.Events
{
public enum SignalState
{
Momentary, // Instantaneous pulse high, compatibility behavior
Low,
High
}
public sealed class SignalReceivedEvent : EntityEventArgs
{
public readonly string Port;
public readonly SignalState State;
public readonly EntityUid? Trigger;
public SignalReceivedEvent(string port, EntityUid? trigger, SignalState state)
{
Port = port;
Trigger = trigger;
State = state;
}
}
}

View File

@@ -0,0 +1,63 @@
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using JetBrains.Annotations;
namespace Content.Server.MachineLinking.System
{
[UsedImplicitly]
public sealed class OrGateSystem : EntitySystem
{
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OrGateComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<OrGateComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnInit(EntityUid uid, OrGateComponent component, ComponentInit args)
{
_signalSystem.EnsureReceiverPorts(uid, "A1", "B1", "A2", "B2");
_signalSystem.EnsureTransmitterPorts(uid, "O1", "O2");
}
private void OnSignalReceived(EntityUid uid, OrGateComponent component, SignalReceivedEvent args)
{
if (args.Port == "A1")
{
component.StateA1 = args.State;
}
else if (args.Port == "B1")
{
component.StateB1 = args.State;
}
else if (args.Port == "A2")
{
component.StateA2 = args.State;
}
else if (args.Port == "B2")
{
component.StateB2 = args.State;
}
// O1 = A1 || B1
var v1 = SignalState.Low;
if (component.StateA1 == SignalState.High || component.StateB1 == SignalState.High)
v1 = SignalState.High;
if (v1 != component.LastO1)
_signalSystem.InvokePort(uid, "O1", v1);
component.LastO1 = v1;
// O2 = A2 || B2
var v2 = SignalState.Low;
if (component.StateA2 == SignalState.High || component.StateB2 == SignalState.High)
v2 = SignalState.High;
if (v2 != component.LastO2)
_signalSystem.InvokePort(uid, "O2", v2);
component.LastO2 = v2;
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Content.Server.DeviceLinking.Events;
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Server.Power.Components;
using Content.Server.Tools;
using Content.Shared.DeviceLinking.Events;
@@ -11,6 +11,7 @@ using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent;
namespace Content.Server.MachineLinking.System
{
@@ -49,7 +50,7 @@ namespace Content.Server.MachineLinking.System
var comp = EnsureComp<SignalReceiverComponent>(uid);
foreach (var port in ports)
{
comp.Inputs.TryAdd(port, new());
comp.Inputs.TryAdd(port, new List<PortIdentifier>());
}
}
@@ -58,7 +59,7 @@ namespace Content.Server.MachineLinking.System
var comp = EnsureComp<SignalTransmitterComponent>(uid);
foreach (var port in ports)
{
comp.Outputs.TryAdd(port, new());
comp.Outputs.TryAdd(port, new List<PortIdentifier>());
}
}
@@ -72,9 +73,11 @@ namespace Content.Server.MachineLinking.System
if (!TryComp(args.Using, out SignalLinkerComponent? linker) ||
!IsLinkerInteractable(args.Using.Value, linker))
{
return;
}
AlternativeVerb verb = new()
var verb = new AlternativeVerb()
{
Text = Loc.GetString("signal-linking-verb-text-link-default"),
IconEntity = args.Using
@@ -130,13 +133,25 @@ namespace Content.Server.MachineLinking.System
}
public void InvokePort(EntityUid uid, string port, SignalTransmitterComponent? component = null)
{
InvokePort(uid, port, SignalState.Momentary, component);
}
public void InvokePort(EntityUid uid, string port, SignalState state, SignalTransmitterComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (state != SignalState.Momentary && state == component.LastState)
{
// no change in output signal
return;
}
if (!component.Outputs.TryGetValue(port, out var receivers))
return;
component.LastState = state;
foreach (var receiver in receivers)
{
var eventArgs = new SignalReceivedEvent(receiver.Port, uid);
@@ -149,6 +164,7 @@ namespace Content.Server.MachineLinking.System
// validate links
Dictionary<EntityUid, SignalReceiverComponent?> uidCache = new();
foreach (var tport in transmitter.Outputs)
{
foreach (var rport in tport.Value)
{
if (!uidCache.TryGetValue(rport.Uid, out var receiver))
@@ -158,6 +174,7 @@ namespace Content.Server.MachineLinking.System
else if (!rpv.Contains(new(uid, tport.Key)))
rpv.Add(new(uid, tport.Key));
}
}
}
private void OnReceiverStartup(EntityUid uid, SignalReceiverComponent receiver, ComponentStartup args)
@@ -208,12 +225,16 @@ namespace Content.Server.MachineLinking.System
private void OnTransmitterInteractUsing(EntityUid uid, SignalTransmitterComponent transmitter, InteractUsingEvent args)
{
if (args.Handled) return;
if (args.Handled)
return;
if (!TryComp(args.Used, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Used, linker) ||
!TryComp(args.User, out ActorComponent? actor))
return;
if (!linker.LinkTX())
return;
linker.SavedTransmitter = uid;
if (!TryComp(linker.SavedReceiver, out SignalReceiverComponent? receiver))
@@ -233,12 +254,16 @@ namespace Content.Server.MachineLinking.System
private void OnReceiverInteractUsing(EntityUid uid, SignalReceiverComponent receiver, InteractUsingEvent args)
{
if (args.Handled) return;
if (args.Handled)
return;
if (!TryComp(args.Used, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Used, linker) ||
!TryComp(args.User, out ActorComponent? actor))
return;
if (!linker.LinkRX())
return;
linker.SavedReceiver = uid;
if (!TryComp(linker.SavedTransmitter, out SignalTransmitterComponent? transmitter))
@@ -274,10 +299,14 @@ namespace Content.Server.MachineLinking.System
var outKeys = transmitter.Outputs.Keys.ToList();
var inKeys = receiver.Inputs.Keys.ToList();
List<(int, int)> links = new();
for (int i = 0; i < outKeys.Count; i++)
for (var i = 0; i < outKeys.Count; i++)
{
foreach (var re in transmitter.Outputs[outKeys[i]])
{
if (re.Uid == receiver.Owner)
links.Add((i, inKeys.IndexOf(re.Port)));
}
}
bui.SetState(new SignalPortsState($"{Name(transmitter.Owner)} ({transmitter.Owner})", outKeys,
$"{Name(receiver.Owner)} ({receiver.Owner})", inKeys, links));
@@ -289,7 +318,9 @@ namespace Content.Server.MachineLinking.System
{
if (!transmitter.Outputs.TryGetValue(args.TransmitterPort, out var linkedReceivers) ||
!receiver.Inputs.TryGetValue(args.ReceiverPort, out var linkedTransmitters))
{
return false;
}
quiet |= !user.HasValue;
@@ -328,10 +359,12 @@ namespace Content.Server.MachineLinking.System
linkedReceivers.Add(new(receiver.Owner, args.ReceiverPort));
linkedTransmitters.Add(new(transmitter.Owner, args.TransmitterPort));
if (!quiet)
{
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-linked-port",
("machine1", transmitter.Owner), ("port1", PortName<TransmitterPortPrototype>(args.TransmitterPort)),
("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(args.ReceiverPort))),
("machine1", transmitter.Owner), ("port1", PortName<TransmitterPortPrototype>(args.TransmitterPort)),
("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(args.ReceiverPort))),
user!.Value, PopupType.Medium);
}
var newLink = new NewLinkEvent(user, transmitter.Owner, args.TransmitterPort, receiver.Owner, args.ReceiverPort);
RaiseLocalEvent(receiver.Owner, newLink);
@@ -353,12 +386,14 @@ namespace Content.Server.MachineLinking.System
if (receivers.Contains(new(receiver.Owner, args.ReceiverPort)) ||
transmitters.Contains(new(transmitter.Owner, args.TransmitterPort)))
{ // link already exists, remove it
{
// link already exists, remove it
if (receivers.Remove(new(receiver.Owner, args.ReceiverPort)) &&
transmitters.Remove(new(transmitter.Owner, args.TransmitterPort)))
{
RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(args.ReceiverPort), true);
RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(args.TransmitterPort), true);
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-unlinked-port",
("machine1", transmitter.Owner), ("port1", PortName<TransmitterPortPrototype>(args.TransmitterPort)),
("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(args.ReceiverPort))),
@@ -397,12 +432,16 @@ namespace Content.Server.MachineLinking.System
return;
foreach (var (port, receivers) in transmitter.Outputs)
{
if (receivers.RemoveAll(id => id.Uid == receiver.Owner) > 0)
RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(port), true);
}
foreach (var (port, transmitters) in receiver.Inputs)
{
if (transmitters.RemoveAll(id => id.Uid == transmitter.Owner) > 0)
RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(port), true);
}
TryUpdateUI(linker, transmitter, receiver);
}
@@ -437,12 +476,16 @@ namespace Content.Server.MachineLinking.System
// First, disconnect existing links.
foreach (var (port, receivers) in transmitter.Outputs)
{
if (receivers.RemoveAll(id => id.Uid == receiver.Owner) > 0)
RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(port), true);
}
foreach (var (port, transmitters) in receiver.Inputs)
{
if (transmitters.RemoveAll(id => id.Uid == transmitter.Owner) > 0)
RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(port), true);
}
// Then make any valid default connections.
foreach (var outPort in transmitter.Outputs.Keys)
@@ -474,6 +517,7 @@ namespace Content.Server.MachineLinking.System
transmitterPower.Provider?.Net == receiverPower.Provider?.Net)
return true;
// TODO: As elsewhere don't use mappos inrange.
return Comp<TransformComponent>(transmitterComponent.Owner).MapPosition.InRange(
Comp<TransformComponent>(receiverComponent.Owner).MapPosition, transmitterComponent.TransmissionRange);
}

View File

@@ -442,6 +442,7 @@ public abstract class SharedDeviceLinkSystem : EntitySystem
private bool InRange(EntityUid sourceUid, EntityUid sinkUid, float range)
{
// TODO: This should be using an existing method and also coordinates inrange instead.
return Transform(sourceUid).MapPosition.InRange(Transform(sinkUid).MapPosition, range);
}

View File

@@ -131,6 +131,10 @@ public abstract class SharedDoorSystem : EntitySystem
if (!Resolve(uid, ref door))
return;
// If no change, return to avoid firing a new DoorStateChangedEvent.
if (state == door.State)
return;
switch (state)
{
case DoorState.Opening:

View File

@@ -1,5 +1,5 @@
signal-port-name-toggle = Autoclose
signal-port-description-toggle = Toggles whether the device should automatically close.
signal-port-name-autoclose = Autoclose
signal-port-description-autoclose = Toggles whether the device should automatically close.
signal-port-name-toggle = Toggle
signal-port-description-toggle = Toggles the state of a device.
@@ -22,6 +22,9 @@ signal-port-description-open = Opens a device.
signal-port-name-close = Close
signal-port-description-close = Closes a device.
signal-port-name-doorbolt = Door bolt
signal-port-description-doorbolt = Toggles door bolt.
signal-port-name-trigger = Trigger
signal-port-description-trigger = Triggers some mechanism on the device.

View File

@@ -13,6 +13,9 @@ signal-port-description-left = This port is invoked whenever the lever is moved
signal-port-name-right = Right
signal-port-description-right = This port is invoked whenever the lever is moved to the rightmost position.
signal-port-name-doorstatus = Door status
signal-port-description-doorstatus = This port is invoked whenever the door's status changes.
signal-port-name-middle = Middle
signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position.

View File

@@ -38,6 +38,11 @@
name: signal-port-name-close
description: signal-port-description-close
- type: sinkPort
id: DoorBolt
name: signal-port-name-doorbolt
description: signal-port-description-doorbolt
- type: sinkPort
id: Trigger
name: signal-port-name-trigger

View File

@@ -34,6 +34,11 @@
description: signal-port-description-middle
defaultLinks: [ Off, Close ]
- type: sourcePort
id: DoorStatus
name: signal-port-name-doorstatus
description: signal-port-description-status
- type: sourcePort
id: OrderSender
name: signal-port-name-order-sender

View File

@@ -89,6 +89,10 @@
- Close
- Toggle
- AutoClose
- DoorBolt
- type: DeviceLinkSource
ports:
- DoorStatus
- type: UserInterface
interfaces:
- key: enum.WiresUiKey.Key

View File

@@ -0,0 +1,26 @@
- type: entity
id: OrGate
name: MS7432
description: Dual 2-Input OR Gate
parent: BaseItem
placement:
mode: SnapgridCenter
snap:
- Wallmount
components:
- type: Anchorable
- type: Sprite
sprite: Objects/Devices/gates.rsi
state: or
- type: Rotatable
- type: OrGate
- type: SignalReceiver
inputs:
A1: []
B1: []
A2: []
B2: []
- type: SignalTransmitter
outputs:
O1: []
O2: []

View File

@@ -72,3 +72,28 @@
id: ArtifactAnalyzerReceiver
name: signal-port-name-artifact-analyzer-receiver
description: signal-port-description-artifact-analyzer-receiver
- type: receiverPort
id: DoorBolt
name: "Bolt"
description: "Bolt door when HIGH."
- type: receiverPort
id: A1
name: "Input A1"
description: "Input A1"
- type: receiverPort
id: B1
name: "Input B1"
description: "Input B1"
- type: receiverPort
id: A2
name: "Input A2"
description: "Input A2"
- type: receiverPort
id: B2
name: "Input B2"
description: "Input B2"

View File

@@ -67,3 +67,18 @@
name: signal-port-name-artifact-analyzer-sender
description: signal-port-description-artifact-analyzer-sender
defaultLinks: [ ArtifactAnalyzerReceiver ]
- type: transmitterPort
id: DoorStatus
name: "Door Status"
description: "HIGH when door is open, LOW when door is closed."
- type: transmitterPort
id: O1
name: "Output 1"
description: "Output 1"
- type: transmitterPort
id: O2
name: "Output 2"
description: "Output 2"

View File

@@ -0,0 +1,14 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Kevin Zheng 2022",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "or"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB