Skip to content

Commit

Permalink
feat(kontakt_io): add support for PIR sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
stakach committed May 6, 2024
1 parent 782ec60 commit 18bc39a
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 18 deletions.
34 changes: 26 additions & 8 deletions drivers/kontakt_io/kio_cloud.cr
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,16 @@ class KontaktIO::KioCloud < PlaceOS::Driver
room_occupancy
end

def telemetry(
tracking_ids : Array(String) = [] of String
) : Array(JSON::Any)
telemetry = [] of JSON::Any
def telemetry(tracking_ids : Array(String)) : Array(Telemetry)
telemetry = [] of Telemetry

params = URI::Params.new
params["endTime"] = Time.utc.to_rfc3339(fraction_digits: 3)
params["startTime"] = 1.minute.ago.to_rfc3339(fraction_digits: 3)
params["startTime"] = 2.minutes.ago.to_rfc3339(fraction_digits: 3)
params["trackingId"] = tracking_ids.map(&.strip.downcase).join(",") unless tracking_ids.empty?

make_request("GET", "/v3/telemetry", params: params) do |data|
resp = Response(JSON::Any).from_json(data)
resp = Response(Telemetry).from_json(data)
telemetry.concat resp.content
resp.page
end
Expand All @@ -164,10 +162,30 @@ class KontaktIO::KioCloud < PlaceOS::Driver
getter occupancy_cache : Hash(Int64, RoomOccupancy) = {} of Int64 => RoomOccupancy

protected def cache_occupancy_counts
occupancy = room_occupancy
cache = Hash(Int64, RoomOccupancy).new(occupancy.size) do |_hash, key|
sensor_to_room = {} of String => Room
rooms.each do |room|
room.room_sensor_ids.each do |sensor_id|
sensor_to_room[sensor_id] = room
end
end

cache = Hash(Int64, RoomOccupancy).new(sensor_to_room.size) do |_hash, key|
raise KeyError.new(%(Missing hash key: "#{key}"))
end

# 3rd party motion sensors
recent_motion = 180_i64
telemetry_data = telemetry(sensor_to_room.keys)
telemetry_data.each do |sensor|
seconds_since = sensor.seconds_since_motion
next unless seconds_since

room = sensor_to_room[sensor.id]
self["room-#{room.id}"] = cache[room.id] = room.to_room_occupancy(seconds_since <= recent_motion, sensor.timestamp)
end

# occupancy counters
occupancy = room_occupancy
occupancy.each { |room| self["room-#{room.room_id}"] = cache[room.room_id] = room }
@occupancy_cache = cache
self[:occupancy_cached_at] = Time.utc.to_unix
Expand Down
76 changes: 69 additions & 7 deletions drivers/kontakt_io/kio_cloud_models.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ module KontaktIO
include JSON::Serializable

getter size : Int32
getter number : Int32
getter number : Int32 { 0 }

@[JSON::Field(key: "totalElements")]
getter total_elements : Int32
getter total_elements : Int32 { 0 }

@[JSON::Field(key: "totalPages")]
getter total_pages : Int32
getter total_pages : Int32 { 0 }
end

class Response(T)
Expand Down Expand Up @@ -126,6 +126,13 @@ module KontaktIO
getter y : Int64?
end

class BuildingShort
include JSON::Serializable

getter id : Int64
getter name : String
end

class Floor
include JSON::Serializable
include JSON::Serializable::Unmapped
Expand All @@ -144,6 +151,8 @@ module KontaktIO

@[JSON::Field(key: "anchorLng")]
getter lng : Float64?

getter building : BuildingShort? = nil
end

class Building
Expand Down Expand Up @@ -187,11 +196,46 @@ module KontaktIO

@[JSON::Field(key: "roomNumber")]
getter room_number : Int64?

@[JSON::Field(key: "roomSensors")]
getter room_sensors : Array(RoomSensor) { [] of RoomSensor }

def room_sensor_ids : Array(String)
room_sensors.map(&.tracking_id)
end

def to_room_occupancy(occupied : Bool, last_update : Time)
RoomOccupancy.new self, occupied, last_update
end
end

struct RoomSensor
include JSON::Serializable
include JSON::Serializable::Unmapped

@[JSON::Field(key: "trackingId")]
getter tracking_id : String
end

struct RoomOccupancy
include JSON::Serializable

def initialize(room : Room, occupied : Bool, last_update : Time)
@room_id = room.id
@room_name = room.name
floor = room.floor
@floor_id = floor.id
@floor_name = floor.name
floor.building.try do |building|
@building_id = building.id
@building_name = building.name
end

@occupancy = occupied ? 1 : 0
@last_update = last_update
@pir = true
end

@[JSON::Field(key: "roomId")]
getter room_id : Int64

Expand All @@ -205,19 +249,37 @@ module KontaktIO
getter floor_name : String?

@[JSON::Field(key: "buildingId")]
getter building_id : Int64?
getter building_id : Int64? = nil

@[JSON::Field(key: "buildingName")]
getter building_name : String?
getter building_name : String? = nil

@[JSON::Field(key: "campusId")]
getter campus_id : Int64?
getter campus_id : Int64? = nil

@[JSON::Field(key: "campusName")]
getter campus_name : String?
getter campus_name : String? = nil

@[JSON::Field(key: "lastUpdate")]
getter last_update : Time
getter occupancy : Int32

getter? pir : Bool = false
end

class Telemetry
include JSON::Serializable
include JSON::Serializable::Unmapped

@[JSON::Field(key: "trackingId")]
getter id : String

@[JSON::Field(key: "secondsSincePirMotion")]
getter seconds_since_motion : Int64?

@[JSON::Field(key: "numberOfPeopleDetected")]
getter number_of_people : Int32?

getter timestamp : Time
end
end
16 changes: 13 additions & 3 deletions drivers/kontakt_io/sensor_service.cr
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,15 @@ class KontaktIO::SensorService < PlaceOS::Driver
nil
end

LOCATION = {"desk", "area"}

def device_locations(zone_id : String, location : String? = nil)
logger.debug { "searching locatable in zone #{zone_id}" }
floor_ids = @zone_lookup[zone_id]?
return [] of Nil unless floor_ids && floor_ids.size > 0
return [] of Nil if location && !LOCATION.includes?(location)

loc_type = "desk"
return [] of Nil if location && location != loc_type

loc = LOCATION
cache = @occupancy_cache
cache.compact_map do |(room_id, space)|
next unless space.floor_id.in?(floor_ids)
Expand All @@ -99,13 +100,21 @@ class KontaktIO::SensorService < PlaceOS::Driver
# temperature = env.temperature.value
# iaq = env.iaq.try &.value
# end
if space.pir?
capacity = 1
loc_type = loc[1]
else
loc_type = loc[0]
capacity = nil
end

{
location: loc_type,
at_location: people_count,
map_id: "room-#{space.room_id}",
level: zone_id,
building: @floor_mappings[space.floor_id.to_s]?.try(&.[](:building_id)),
capacity: capacity,

kontakt_io_room: space.room_name,
}
Expand All @@ -125,6 +134,7 @@ class KontaktIO::SensorService < PlaceOS::Driver

case id
when "people"
return nil if room.pir?
build_sensor_details(room, :people_count)
when "presence"
build_sensor_details(room, :presence)
Expand Down
1 change: 1 addition & 0 deletions drivers/kontakt_io/sensor_service_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ DriverSpecs.mock_driver "KontaktIO::SensorService" do
"map_id" => "room-195835",
"level" => "zone-level",
"building" => "zone-building",
"capacity" => nil,
"kontakt_io_room" => "Open Pod",
}])
end
Expand Down

0 comments on commit 18bc39a

Please sign in to comment.