|
| 1 | +require "placeos-driver" |
| 2 | +require "placeos-driver/interface/lockers" |
| 3 | + |
| 4 | +class Place::Demo::Lockers < PlaceOS::Driver |
| 5 | + include Interface::Lockers |
| 6 | + alias PlaceLocker = PlaceOS::Driver::Interface::Lockers::PlaceLocker |
| 7 | + |
| 8 | + descriptive_name "Locker Testing" |
| 9 | + generic_name :DemoLockers |
| 10 | + description %(used for end to end testing of locker interfaces) |
| 11 | + |
| 12 | + accessor staff_api : StaffAPI_1 |
| 13 | + accessor locations : LocationServices_1 |
| 14 | + |
| 15 | + getter building_id : String do |
| 16 | + locations.building_id.get.as_s |
| 17 | + end |
| 18 | + |
| 19 | + getter levels : Array(String) do |
| 20 | + staff_api.systems_in_building(building_id).get.as_h.keys |
| 21 | + end |
| 22 | + |
| 23 | + getter locker_banks : Hash(String, LockerBank) do |
| 24 | + lookup = {} of String => LockerBank |
| 25 | + levels.flat_map { |level_id| |
| 26 | + banks = lockers_details(level_id) |
| 27 | + banks.try(&.each { |bank| |
| 28 | + bank.level_id = level_id |
| 29 | + }) |
| 30 | + banks |
| 31 | + }.each { |bank| lookup[bank.id] = bank } |
| 32 | + lookup |
| 33 | + end |
| 34 | + |
| 35 | + class Locker |
| 36 | + include JSON::Serializable |
| 37 | + |
| 38 | + getter id : String |
| 39 | + getter name : String |
| 40 | + getter bookable : Bool { false } |
| 41 | + |
| 42 | + # for tracking, not part of metadata |
| 43 | + property allocated_to : String? = nil |
| 44 | + property allocated_until : Time? = nil |
| 45 | + property level_id : String? = nil |
| 46 | + property shared_with : Array(String) = [] of String |
| 47 | + |
| 48 | + def release |
| 49 | + @allocated_to = nil |
| 50 | + @allocated_until = nil |
| 51 | + @shared_with = [] of String |
| 52 | + end |
| 53 | + end |
| 54 | + |
| 55 | + class LockerBank |
| 56 | + include JSON::Serializable |
| 57 | + |
| 58 | + getter id : String |
| 59 | + getter name : String |
| 60 | + getter lockers : Array(Locker) |
| 61 | + |
| 62 | + getter locker_hash : Hash(String, Locker) do |
| 63 | + lookup = {} of String => Locker |
| 64 | + level = self.level_id |
| 65 | + lockers.each do |locker| |
| 66 | + locker.level_id = level |
| 67 | + lookup[locker.id] = locker |
| 68 | + end |
| 69 | + lookup |
| 70 | + end |
| 71 | + |
| 72 | + property level_id : String? = nil |
| 73 | + end |
| 74 | + |
| 75 | + def lockers_details(level_id : String) : Array(LockerBank) |
| 76 | + lockers = staff_api.metadata(level_id, "lockers").get.dig?("lockers", "details") |
| 77 | + return [] of LockerBank unless lockers |
| 78 | + Array(LockerBank).from_json(lockers.to_json) |
| 79 | + end |
| 80 | + |
| 81 | + class ::PlaceOS::Driver::Interface::Lockers::PlaceLocker |
| 82 | + def initialize(@bank_id, locker : Place::Demo::Lockers::Locker, @building = nil) |
| 83 | + @locker_id = locker.id |
| 84 | + @locker_name = locker.name |
| 85 | + @mac = "lb=#{@bank_id}&lk=#{locker.id}" |
| 86 | + if time = locker.allocated_until |
| 87 | + if time > Time.utc |
| 88 | + in_use = true |
| 89 | + @expires_at = time |
| 90 | + else |
| 91 | + in_use = false |
| 92 | + @expires_at = nil |
| 93 | + end |
| 94 | + elsif allocated_to = locker.allocated_to |
| 95 | + in_use = true |
| 96 | + @expires_at = nil |
| 97 | + else |
| 98 | + in_use = false |
| 99 | + @expires_at = nil |
| 100 | + end |
| 101 | + @allocated = in_use |
| 102 | + @level = locker.level_id |
| 103 | + end |
| 104 | + end |
| 105 | + |
| 106 | + # allocates a locker now, the allocation may expire |
| 107 | + @[Security(Level::Administrator)] |
| 108 | + def locker_allocate( |
| 109 | + # PlaceOS user id |
| 110 | + user_id : String, |
| 111 | + |
| 112 | + # the locker location |
| 113 | + bank_id : String | Int64, |
| 114 | + |
| 115 | + # allocates a random locker if this is nil |
| 116 | + locker_id : String | Int64? = nil, |
| 117 | + |
| 118 | + # attempts to create a booking that expires at the time specified |
| 119 | + expires_at : Int64? = nil |
| 120 | + ) : PlaceLocker |
| 121 | + locker = locker_banks[bank_id.to_s].locker_hash[locker_id.to_s] |
| 122 | + locker.allocated_to = user_id |
| 123 | + locker.allocated_until = Time.unix(expires_at) if expires_at |
| 124 | + PlaceLocker.new(bank_id, locker, building_id) |
| 125 | + end |
| 126 | + |
| 127 | + # return the locker to the pool |
| 128 | + @[Security(Level::Administrator)] |
| 129 | + def locker_release( |
| 130 | + bank_id : String | Int64, |
| 131 | + locker_id : String | Int64, |
| 132 | + |
| 133 | + # release / unshare just this user - otherwise release the whole locker |
| 134 | + owner_id : String? = nil |
| 135 | + ) : Nil |
| 136 | + locker = locker_banks[bank_id.to_s].locker_hash[locker_id.to_s] |
| 137 | + if locker.allocated_to == owner_id |
| 138 | + locker.release |
| 139 | + else |
| 140 | + locker.shared_with.delete(owner_id) |
| 141 | + end |
| 142 | + end |
| 143 | + |
| 144 | + # a list of lockers that are allocated to the user |
| 145 | + @[Security(Level::Administrator)] |
| 146 | + def lockers_allocated_to(user_id : String) : Array(PlaceLocker) |
| 147 | + now = Time.utc |
| 148 | + building = building_id |
| 149 | + |
| 150 | + locker_banks.values.flat_map do |bank| |
| 151 | + bank.locker_hash.values.compact_map do |locker| |
| 152 | + if locker.allocated_to == user_id |
| 153 | + if time = locker.allocated_until |
| 154 | + PlaceLocker.new(bank.id, locker, building) if time > now |
| 155 | + else |
| 156 | + PlaceLocker.new(bank.id, locker, building) |
| 157 | + end |
| 158 | + end |
| 159 | + end |
| 160 | + end |
| 161 | + end |
| 162 | + |
| 163 | + @[Security(Level::Administrator)] |
| 164 | + def locker_share( |
| 165 | + bank_id : String | Int64, |
| 166 | + locker_id : String | Int64, |
| 167 | + owner_id : String, |
| 168 | + share_with : String |
| 169 | + ) : Nil |
| 170 | + locker = locker_banks[bank_id.to_s].locker_hash[locker_id.to_s] |
| 171 | + perform_share = false |
| 172 | + if locker.allocated_to == owner_id |
| 173 | + if time = locker.allocated_until |
| 174 | + perform_share = time > Time.utc |
| 175 | + else |
| 176 | + perform_share = true |
| 177 | + end |
| 178 | + end |
| 179 | + |
| 180 | + if perform_share |
| 181 | + locker.shared_with << share_with |
| 182 | + locker.shared_with.uniq! |
| 183 | + end |
| 184 | + end |
| 185 | + |
| 186 | + @[Security(Level::Administrator)] |
| 187 | + def locker_unshare( |
| 188 | + bank_id : String | Int64, |
| 189 | + locker_id : String | Int64, |
| 190 | + owner_id : String, |
| 191 | + # the individual you previously shared with (optional) |
| 192 | + shared_with_id : String? = nil |
| 193 | + ) : Nil |
| 194 | + locker = locker_banks[bank_id.to_s].locker_hash[locker_id.to_s] |
| 195 | + perform_share = false |
| 196 | + if locker.allocated_to == owner_id |
| 197 | + if time = locker.allocated_until |
| 198 | + perform_share = time > Time.utc |
| 199 | + else |
| 200 | + perform_share = true |
| 201 | + end |
| 202 | + end |
| 203 | + |
| 204 | + if perform_share |
| 205 | + if shared_with_id |
| 206 | + locker.shared_with.delete shared_with_id |
| 207 | + else |
| 208 | + locker.shared_with = [] of String |
| 209 | + end |
| 210 | + end |
| 211 | + end |
| 212 | + |
| 213 | + # a list of user-ids that the locker is shared with. |
| 214 | + # this can be placeos user ids or emails |
| 215 | + @[Security(Level::Administrator)] |
| 216 | + def locker_shared_with( |
| 217 | + bank_id : String | Int64, |
| 218 | + locker_id : String | Int64, |
| 219 | + owner_id : String |
| 220 | + ) : Array(String) |
| 221 | + locker = locker_banks[bank_id.to_s].locker_hash[locker_id.to_s] |
| 222 | + perform_share = false |
| 223 | + if locker.allocated_to == owner_id |
| 224 | + if time = locker.allocated_until |
| 225 | + perform_share = time > Time.utc |
| 226 | + else |
| 227 | + perform_share = true |
| 228 | + end |
| 229 | + end |
| 230 | + |
| 231 | + if perform_share |
| 232 | + locker.shared_with |
| 233 | + else |
| 234 | + [] of String |
| 235 | + end |
| 236 | + end |
| 237 | + |
| 238 | + @[Security(Level::Administrator)] |
| 239 | + def locker_unlock( |
| 240 | + bank_id : String | Int64, |
| 241 | + locker_id : String | Int64, |
| 242 | + |
| 243 | + # sometimes required by locker systems |
| 244 | + owner_id : String? = nil, |
| 245 | + # time in seconds the locker should be unlocked |
| 246 | + # (can be ignored if not implemented) |
| 247 | + open_time : Int32 = 60, |
| 248 | + # optional pin code - if user entered from a kiosk |
| 249 | + pin_code : String? = nil |
| 250 | + ) : Nil |
| 251 | + end |
| 252 | + |
| 253 | + # =================================== |
| 254 | + # Locatable Interface functions |
| 255 | + # =================================== |
| 256 | + def locate_user(email : String? = nil, username : String? = nil) |
| 257 | + logger.debug { "sensor incapable of locating #{email} or #{username}" } |
| 258 | + [] of Nil |
| 259 | + end |
| 260 | + |
| 261 | + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) |
| 262 | + logger.debug { "sensor incapable of tracking #{email} or #{username}" } |
| 263 | + # we could find the floorsense user, grab the reservations the user has |
| 264 | + # and list them here, but probably not amazingly useful |
| 265 | + [] of String |
| 266 | + end |
| 267 | + |
| 268 | + def check_ownership_of(mac_address : String) : OwnershipMAC? |
| 269 | + # "lb=#{@bank_id}&lk=#{locker.id}" |
| 270 | + return nil unless mac_address.starts_with?("lb=") |
| 271 | + floor_mac = URI::Params.parse mac_address |
| 272 | + locker_bank = floor_mac["lb"] |
| 273 | + locker_key = floor_mac["lk"] |
| 274 | + locker = locker_banks[locker_bank].locker_hash[locker_key] |
| 275 | + |
| 276 | + has_reservation = false |
| 277 | + if user_id = locker.allocated_to |
| 278 | + if time = locker.allocated_until |
| 279 | + has_reservation = time > Time.utc |
| 280 | + else |
| 281 | + has_reservation = true |
| 282 | + end |
| 283 | + end |
| 284 | + |
| 285 | + if has_reservation |
| 286 | + { |
| 287 | + location: "locker", |
| 288 | + assigned_to: staff_api.user(locker.allocated_to).get["email"].as_s, |
| 289 | + mac_address: mac_address, |
| 290 | + } |
| 291 | + end |
| 292 | + rescue |
| 293 | + nil |
| 294 | + end |
| 295 | + |
| 296 | + def device_locations(zone_id : String, location : String? = nil) |
| 297 | + logger.debug { "searching lockers in zone #{zone_id}" } |
| 298 | + return [] of Nil if location && location != "locker" |
| 299 | + |
| 300 | + building = building_id |
| 301 | + level_zone = zone_id == building ? nil : zone_id |
| 302 | + return [] of Nil if level_zone && !level_zone.in?(levels) |
| 303 | + |
| 304 | + now = Time.utc |
| 305 | + locker_banks.values.flat_map do |bank| |
| 306 | + next [] of PlaceLocker if level_zone && bank.level_id != level_zone |
| 307 | + |
| 308 | + bank.locker_hash.values.compact_map do |locker| |
| 309 | + if locker.allocated_to |
| 310 | + if time = locker.allocated_until |
| 311 | + PlaceLocker.new(bank.id, locker, building) if time > now |
| 312 | + else |
| 313 | + PlaceLocker.new(bank.id, locker, building) |
| 314 | + end |
| 315 | + end |
| 316 | + end |
| 317 | + end |
| 318 | + end |
| 319 | +end |
0 commit comments