Skip to content

Commit

Permalink
Adds accessory consumer for extended accessories.
Browse files Browse the repository at this point in the history
Generates the dcc packets on the rails.
  • Loading branch information
balazsracz committed Feb 1, 2024
1 parent 6f70967 commit 23683b5
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 0 deletions.
62 changes: 62 additions & 0 deletions src/openlcb/DccAccyConsumer.cxxtest
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,66 @@ TEST_F(DccAccyTest, packet_throw)
wait();
}


class DccExtAccyTest : public AsyncNodeTest
{
protected:
DccExtAccyTest()
{
wait();
}

StrictMock<MockPacketQueue> trackSendQueue_;
DccExtAccyConsumer consumer_{node_, &trackSendQueue_};
};

TEST_F(DccExtAccyTest, create)
{
}

TEST_F(DccExtAccyTest, global_identify)
{
clear_expect(true);
// Consumer range for one very long range (11 bit plus 8 bit).
expect_packet(":X194A422AN010102000107FFFF;");

// identify events addressed.
send_packet(":X19968111N022A;");
wait();
}


TEST_F(DccExtAccyTest, identify_unknown)
{
// unknown identify
send_packet_and_expect_response(
":X198F4111N0101020001035a77;", ":X194C722AN0101020001035a77;");
}

TEST_F(DccExtAccyTest, packet_throw)
{
uint8_t hdr = 0b01100100;
EXPECT_CALL(trackSendQueue_,
arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xa5, _)));

// set to 0xa5
send_packet(":X195B4111N010102000106c5a5;");


EXPECT_CALL(trackSendQueue_,
arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0x00, _)));

// set to 0x00
send_packet(":X195B4111N010102000106c500;");

EXPECT_CALL(trackSendQueue_,
arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xff, _)));

// set to 0xff
send_packet(":X195B4111N010102000106c5ff;");

wait();
}


} // namespace openlcb
144 changes: 144 additions & 0 deletions src/openlcb/DccAccyConsumer.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,150 @@ private:
dcc::TrackIf *track_;
};

/// Base (generic protocol) implementation of the DCC extended accessory
/// consumer. Unlike the basic accessory version, this one does not remember
/// the last set state.
class DccExtAccyConsumerBase : public SimpleEventHandler
{
protected:
/// How may addresses are there for extended accessories.
static constexpr unsigned NUM_ADDRESS = 2048;
/// How may aspects are supported per accessory.
static constexpr unsigned NUM_ASPECT = 256;
/// Total number of events we are listening for.
static constexpr unsigned NUM_EVENT = NUM_ASPECT * NUM_ADDRESS;

/// Constructs a listener for DCC extended accessory control.
/// @param node is the virtual node that will be listening for events and
/// responding to Identify messages.
DccExtAccyConsumerBase(Node *node)
: node_(node)
{
EventRegistry::instance()->register_handler(
EventRegistryEntry(
this, TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE),
11+8 /*number of bits*/);
}

/// Destructor.
~DccExtAccyConsumerBase()
{
EventRegistry::instance()->unregister_handler(this);
}

void handle_identify_global(const EventRegistryEntry &registry_entry,
EventReport *event, BarrierNotifiable *done) OVERRIDE
{
AutoNotify an(done);
if (event->dst_node && event->dst_node != node_)
{
return;
}
event->event_write_helper<1>()->WriteAsync(node_,
Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(),
eventid_to_buffer(EncodeRange(
TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE, NUM_EVENT - 1)),
done->new_child());
}

void handle_event_report(const EventRegistryEntry &registry_entry,
EventReport *event, BarrierNotifiable *done) override
{
AutoNotify an(done);
if (!parse_event(event->event))
{
return;
}
send_accy_command();
}

void handle_identify_consumer(const EventRegistryEntry &entry,
EventReport *event, BarrierNotifiable *done) override
{
AutoNotify an(done);
if (!parse_event(event->event))
{
return;
}
event->event_write_helper<1>()->WriteAsync(node_,
Defs::MTI_CONSUMER_IDENTIFIED_UNKNOWN, WriteHelper::global(),
eventid_to_buffer(event->event), done->new_child());
}

/// Send the actual accessory command.
virtual void send_accy_command() = 0;

/// Parses an event into an openlcb accessory offset.
/// @return true if the event is in the accessory range, false if this
/// event can be ignored.
/// @param on_off will be set to true if this is an activate event, false
/// if it is an inactivate event.
/// @param ofs will be set to the offset in the state_ arrays.
/// @param mask will be set to a single bit value that marks the location
/// in the state_ arrays.
bool parse_event(EventId event)
{
if (event >= TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE &&
event <
TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE + NUM_EVENT)
{
aspect_ = event & 0xff;
dccAddress_ = (event >> 8) & (NUM_ADDRESS - 1);
return true;
}
else
{
return false;
}
}


/// Parsed event state: dcc address (0..2047) without inverting or encoding.
unsigned dccAddress_ : 11;
/// Parsed event state: the aspect commanded.
unsigned aspect_ : 8;

/// OpenLCB node to export the consumer on.
Node *node_;
};

/// Specialized (DCC protocol) implementation of a DCC extended accessory
/// consumer.
class DccExtAccyConsumer : public DccExtAccyConsumerBase
{
public:
/// Constructs a listener for DCC accessory control.
/// @param node is the virtual node that will be listening for events and
/// responding to Identify messages.
/// @param track is the interface through which we will be writing DCC
/// accessory packets.
DccExtAccyConsumer(Node *node, dcc::TrackIf *track)
: DccExtAccyConsumerBase(node)
, track_(track)
{
}

/// Destructor.
~DccExtAccyConsumer()
{
}

private:
/// Send the actual accessory command.
void send_accy_command() override
{
dcc::TrackIf::message_type *pkt;
mainBufferPool->alloc(&pkt);
pkt->data()->add_dcc_ext_accessory(dccAddress_, aspect_);
pkt->data()->packet_header.rept_count = 3;
track_->send(pkt);
}

/// Track to send DCC packets to.
dcc::TrackIf *track_;
};


} // namespace openlcb

#endif // _OPENLCB_DCCACCYCONSUMER_HXX_

0 comments on commit 23683b5

Please sign in to comment.