private async Task ProcessEvent(RfidEvent ev)
        {
            var boat = await this.bsCache.GetResourceFromRfidTagAsync(ev.Id);
            var makerChannelKey = (await this.bsCache.GetBotUserAsync()).MakerChannelKey();

            string doorName = "Unknown";

            if (ev.Antenna < this.currentClub.DoorNames.Count)
            {
                doorName = this.currentClub.DoorNames[ev.Antenna];
            }

            this.LogBoatEvent(boat, doorName, ev);

            if (boat == null)
            {
                await this.SendIftttTrigger(makerChannelKey, "new_tag", ev.Timestamp.Value.ToString(), ev.Id, doorName);
                return;
            }

            await this.SendIftttTrigger(makerChannelKey, ev.EventType, ev.Timestamp.Value.ToString(), boat.Name(), doorName);

            //
            // TODO: Looks for a reservation to see if we can just annotate it with in/out times.
            // Otherwise, we need to create a reservation with an "unknown" rower to log the usage.
            //
        }
 private void LogBoatEvent(JToken boat, string doorName, RfidEvent ev)
 {
     if (boat != null)
     {
         this.telemetryClient.TrackEvent(
             ev.EventType,
             new Dictionary<string, string>
             {
                 ["ClubId"] = this.currentClub.Id,
                 ["Timestamp"] = ev.Timestamp.Value.ToString(),
                 ["TagId"] = ev.Id,
                 ["BoatName"] = boat.Name(),
                 ["DoorName"] = doorName
             });
     }
     else
     {
         this.telemetryClient.TrackEvent(
             "new_tag",
             new Dictionary<string, string>
             {
                 ["ClubId"] = this.currentClub.Id,
                 ["Timestamp"] = ev.Timestamp.Value.ToString(),
                 ["DoorName"] = doorName,
                 ["TagId"] = ev.Id,
             });
     }
 }
            /// <summary>
            /// It will be common to get two events for the same boat close together since we
            /// normally have two tags per boat. We want to make sure we only process the first
            /// event in this case and ignore the redundant ones.
            /// </summary>
            /// <param name="ev">An incoming event</param>
            /// <returns>True if the event is redundant</returns>
            public async Task<bool> IsEventRedundantAsync(RfidEvent ev)
            {
                if (!ev.Timestamp.HasValue)
                {
                    ev.Timestamp = DateTime.Now;
                }

                var boat = await this.GetResourceFromRfidTagAsync(ev.Id);

                if (boat == null)
                {
                    // If the tag isn't associated with a boat, it can't be redundant
                    // but we also don't want to cache it.
                    return false;
                }

                var boatId = boat.ResourceId();

                RfidEvent lastEvent;

                bool isRedundant = true;

                // If no event for this boat, this isn't redundant
                if (!this.mapResourceIdToLastEvent.TryGetValue(boatId, out lastEvent))
                {
                    isRedundant = false;
                }
                else if (lastEvent.Timestamp.Value + EventLifetime < DateTime.Now)
                {
                    // If we haven't seen an event for this boat in a while, this isn't redundant
                    isRedundant = false;
                }
                else if (lastEvent.EventType != ev.EventType || lastEvent.Antenna != ev.Antenna)
                {
                    // If the door or direction are different, this is a new event
                    isRedundant = false;
                }

                if (!isRedundant)
                {
                    this.mapResourceIdToLastEvent.TryAdd(boatId, ev);
                }

                return isRedundant;
            }