Is there a better way to summarize a table containing events of different types by identifier?
There is no way where you don't have to touch your query when new types are introduced, because the number and type of the columns of a query must be known to the database before the query is actually run (they are evaluated at parse time).
But you can simplify your query by using filtered aggregation to get the different created columns in the output. You also don't need a full join as you make sen_sensor the driving table. A left join is enough in this case (assuming you never have sensors in the even table that don't exist in the sensor table).
SELECT x.belongs_to,
       x.sensor_id,
       x.name,
       y.event_count,
       y.temperature[1] as temperature,
       y.temperature_created,
       y.human_motion_created,
       y.presence_created,
       y.heartbeat_created,
       y.startup_created
FROM sen_sensor x
  left join (
      select sensor_id, 
             count(*) as event_count,
             array_agg(temperature order by created desc) filter (where type = 'TEMPERATURE') temperature,
             max(created) filter (where type = 'TEMPERATURE') as temperature_created,
             max(created) filter (where type = 'HUMAN_MOTION') as human_motion_created,
             max(created) filter (where type = 'PRESENCE') as presence_created,
             max(created) filter (where type = 'HEARTBEAT') as heartbeat_created,
             max(created) filter (where type = 'STARTUP') as startup_created
      from sen_sensor_event
      group by sensor_id
  ) as y on y.sensor_id = x.sensor_id      

The array_agg(temperature ...) is necessary to get the &quot;latest temperature&quot; as Postgres has no &quot;last_value&quot; as an ordered-set aggregate function (only as a window function). Aggregating the values into a sorted array and then picking the first one serves essentially the same purpose.
When a new type is added, you only add a new max(...) filter (...) expression, which still needs a manual change, but is a lot less to type. In fact, you could have a function (or procedure) that generates the necessary SQL dynamically based on the available types.
This should also be substantially faster than your solution.