diff --git a/CHANGELOG-S2.rst b/CHANGELOG-S2.rst index e45c802d..12887b18 100644 --- a/CHANGELOG-S2.rst +++ b/CHANGELOG-S2.rst @@ -16,77 +16,144 @@ Other changes: see `Unreleased in the changelog /s``, a negative expected usage as none, with a warning, and a leakage behaviour is recorded as a storage efficiency each time a schedule is asked for (see Changed). Without a sensor, that declaration is not recorded [`#244 `__, `#259 `__, `#271 `__, `#274 `__] -* ``FRBCSimple`` takes an ``energy_unit``, defaulting to ``kWh``, which a state of charge, the fill-level band around it, a fill rate and a usage forecast (the latter two per second) are all recorded in. It is one of ``J``, ``kJ``, ``MJ``, ``Wh``, ``kWh`` and ``MWh``, and is refused when the handler is made otherwise. A state of charge goes into a flex model in ``kWh`` or ``MWh``, converted from any other of these, since FlexMeasures takes no other [`#244 `__, `#260 `__] -* ``FRBCSimple`` takes a ``fill_level_scale``, defaulting to 1, which every fill level and fill rate it sends to FlexMeasures is multiplied by: the state of charge, the flex model, the fill-level band, the fill rate, the usage forecast and the storage efficiency. Translating a schedule back into instructions keeps the resource manager's own units, since it matches fill levels against the operation modes stated in them. A scale of zero or below is refused. It also takes a ``power_unit``, defaulting to ``W``, which an actuator's power is converted into and labelled with when it is posted [`#236 `__, `#244 `__, `#245 `__, `#252 `__] -* ``FRBCSimple`` takes a ``hard_bound_factor``, defaulting to 1.5, which sets how far above the declared fill-level range the hard upper bound of a schedule sits. The bound is widened further where the state of charge a schedule starts from is above it, and a factor below 1 is refused, since FlexMeasures clips the band into the hard bounds and a bound inside the declared range would narrow the band to something the resource manager never asked for [`#245 `__] -* ``FRBCSimple`` takes an optional ``charging_efficiency_sensor_id``, records on it what the actuator's operation modes say about converting drawn power into stored energy, and asks for a schedule against it as ``charging-efficiency``. The ratio between a mode's fill rate and the power it draws is a heat pump's coefficient of performance, and without it the scheduler plans the storage as if an actuator converted power into stored energy one for one, so a plan reaches a fill-level target at a power the actuator cannot deliver it at. FlexMeasures takes one for the whole device, so where a mode converts differently at different fill levels, the element that applies where the schedule starts answers, and where the modes differ, the lowest goes out, and the choice is logged. A mode that draws power while the storage does not fill, such as a standby or circulation mode, states none, rather than an efficiency of zero that would tell the scheduler no power could fill the storage at all, and a mode reserved for abnormal conditions is not counted. It is recorded once per window, and recorded again for the next schedule where FlexMeasures did not confirm storing it; where it cannot be recorded, the schedule is asked for without it, and the log says so [`#253 `__, `#256 `__, `#272 `__, `#273 `__] -* ``fill_rate_in_watts()`` and ``operation_mode_conversion_efficiency()``, which state a fill rate in the watts S2 states a power range in, and take the ratio between the two at a given fill level. A fill rate is per second of whatever unit a fill level is recorded in, so a kilowatt hour per second is 3.6 million watts, and an energy unit neither knows is refused rather than converted by a factor that is wrong by thousands [`#253 `__, `#256 `__] -* ``FRBCSimple`` takes ``schedule_attempts``, defaulting to five, and ``schedule_retry_interval``, defaulting to two seconds, which bound how often a schedule is asked for and how long between two attempts; fewer than one attempt is refused. v0.9.0 asked once, so any failure cost the storage its instruction for that window, while the resource manager kept polling for it. A scheduling job that failed inside the server is asked for again, unless its cause fails identically on every attempt, as a scheduler colliding with its own belief or an infeasible problem does. So is a server that could not be reached or answered with a 5xx, and a bare HTTP 400 from a FlexMeasures older than v0.33.0, which reports a failed job no other way. A job no worker picked up in time is asked about once more. Giving up is logged as an error that says the storage is left without an instruction [`#254 `__, `#273 `__] -* ``FRBCSimple`` takes an optional ``production_price_sensor_id``, for a site where feeding back is priced differently from drawing, as under a feed-in tariff. Without it, both directions are priced by ``price_sensor_id``, as before [`#261 `__] -* ``FRBCSimple`` takes ``relax_site_capacity``: ``True`` lets a schedule breach the site's connection capacity at a price, for a site whose capacity is a target set by others, and ``False`` keeps it a hard limit. Unset, it is left to FlexMeasures, which since v1.0 relaxes it by default, so a home whose connection is a physical limit has to pass ``False`` [`#261 `__] -* An FRBC handler logs the instructions of a batch once, at debug level, by operation-mode name [`#261 `__] -* An FRBC handler tracks the instruction batch the resource manager is working from, and, with its ``send_revocations`` attribute set to ``True``, revokes the instructions of a batch it replaces that are still pending, that is, with no status reported yet, or reported as ``NEW`` or ``ACCEPTED``. Off by default, since a resource manager is not required to honour revocations [`#237 `__] -* ``FRBC.filter_instruction_transitions()``, ``FRBC.send_instruction_batch()`` and ``FRBC.system_description_fingerprint()``, which a handler uses to drop instructions that repeat their predecessor, to send a batch that replaces the one before it (dropped where a newer request arrived meanwhile), and to tell a changed system description from a re-sent one [`#237 `__] -* ``ControlTypeHandler.send_message()``, which handlers use instead of putting messages on the CEM's sending queue themselves [`#237 `__] -* ``FRBCSimple`` takes an optional ``fill_rate_sensor_id``, and records the actuator's fill rate there, as a rate of fill level in ``/s`` rather than in a power unit, since S2 states a fill rate as fill level per second, which for a heat buffer is not electricity at all [`#236 `__, `#244 `__] -* ``flexmeasures_client.s2.clock``, with a ``WallClock`` and a ``SimulatedClock``, and ``CEM(clock=...)``: the time a CEM and every handler it registers go by, timers and the moment a state of charge is known as of included [`#264 `__, `#268 `__] -* ``CEM(accept_simulation_metadata=True)``, which takes a co-simulation's messages wrapped with the simulated time (``S2Wrapper``) and sets its clock from them. Off by default, a wrapped message is refused as invalid. The example server turns it on with ``S2_ACCEPT_SIMULATION_METADATA`` [`#264 `__] -* ``flexmeasures_client.s2.site``: ``FRBC_SITE_SENSORS``, ``ensure_site`` and ``FRBCSiteConfigurator``, which gives each resource manager a FlexMeasures site of its own, named after it, and an FRBC handler wired to it [`#265 `__] -* ``CEM(site_configurator=...)``, which sets a resource manager's site up once its details arrive, answering its messages with a temporary error until then. The example server does so with ``S2_SITE_PER_RESOURCE_MANAGER`` [`#265 `__] -* ``examples/S2-co-simulation``, a FlexMeasures stack with one CEM per simulated home, for a co-simulation whose resource managers connect over websockets [`#265 `__, `#267 `__] -* ``flexmeasures_client.s2.measurements.MeasurementRecorder`` and ``CEM(measurement_recorder=...)``: where a resource manager's measured power is recorded, one per server so that a reconnecting resource manager is not recorded twice [`#266 `__] +* ``FRBCSimple`` can record what a resource manager declares about its storage, so it can be seen and planned with. Give it any of the new ``soc_minima_sensor_id``, ``soc_maxima_sensor_id``, ``usage_forecast_sensor_id`` and ``leakage_behaviour_sensor_id``; a declaration without a sensor is not recorded. + + - A fill-level target profile is recorded on the minima and maxima sensors, if both are given. It is recorded on the handler's ``resolution`` steps, starting from the step its start falls in. It is kept inside the declared fill-level range: a target past either end, such as a night setback below the range, is cut back to the range, and a target band wholly outside the range is recorded at its nearest end, with a warning. If one of the two bounds cannot be recorded, this is logged and the other is still recorded. + - A usage forecast is recorded in ``/s``. A negative expected usage is recorded as zero, with a warning. + - A leakage behaviour is recorded as a storage efficiency, each time a schedule is requested (see Changed). + + [`#244 `__, `#259 `__, `#271 `__, `#274 `__] +* ``FRBCSimple`` takes an ``energy_unit`` (default ``kWh``). The state of charge, the fill-level band, the fill rate and the usage forecast are recorded in this unit (the last two per second). Allowed units are ``J``, ``kJ``, ``MJ``, ``Wh``, ``kWh`` and ``MWh``; any other unit is refused when the handler is created. In a flex model, the state of charge is stated in ``kWh`` or ``MWh``, converted from the other units, because FlexMeasures accepts no others [`#244 `__, `#260 `__] +* ``FRBCSimple`` takes a ``fill_level_scale`` (default 1). Every fill level and fill rate sent to FlexMeasures is multiplied by it: the state of charge, the flex model, the fill-level band, the fill rate, the usage forecast and the storage efficiency. Translating a schedule into instructions keeps the resource manager's own units, because it matches fill levels against the operation modes, which are stated in those units. A scale of zero or below is refused. ``FRBCSimple`` also takes a ``power_unit`` (default ``W``), which an actuator's power is converted into and posted in [`#236 `__, `#244 `__, `#245 `__, `#252 `__] +* ``FRBCSimple`` takes a ``hard_bound_factor`` (default 1.5). It sets how far above the declared fill-level range a schedule's hard upper bound lies. If a schedule starts from a state of charge above that bound, the bound is raised to include it. A factor below 1 is refused: FlexMeasures clips the band to the hard bounds, so a hard upper bound inside the declared range would narrow the band to something the resource manager never asked for [`#245 `__] +* ``FRBCSimple`` can take the actuator's charging efficiency into account. Give it the new ``charging_efficiency_sensor_id``: it records there what the operation modes say about converting drawn power into stored energy, and requests a schedule with it as ``charging-efficiency``. For a heat pump, this ratio is its coefficient of performance. Without it, the scheduler assumes power converts into stored energy one to one, and plans to reach fill-level targets at a power the actuator cannot deliver. + + - FlexMeasures takes one efficiency per device. If a mode's efficiency depends on the fill level, the value where the schedule starts is used. If modes differ, the lowest is used, and the choice is logged. + - A mode that draws power without filling the storage, such as standby or circulation, does not count. Counting it as zero would tell the scheduler that the storage cannot be filled at all. Modes reserved for abnormal conditions do not count either. + - The efficiency is recorded once per window. It is recorded again for the next schedule if FlexMeasures did not confirm storing it. If it cannot be recorded, the schedule is requested without it, and this is logged. + + [`#253 `__, `#256 `__, `#272 `__, `#273 `__] +* ``fill_rate_in_watts()`` converts a fill rate into watts, the unit of S2 power ranges. ``operation_mode_conversion_efficiency()`` gives the ratio between the two at a given fill level. A fill rate is per second, in the unit fill levels are recorded in, so one kilowatt hour per second is 3.6 million watts. An unknown energy unit is refused, rather than converted with a factor that is off by thousands [`#253 `__, `#256 `__] +* ``FRBCSimple`` retries a failed schedule request. Two new arguments set the limits: ``schedule_attempts`` (default 5, at least 1) and ``schedule_retry_interval`` (default 2 seconds). In v0.9.0, a single failure left the storage without an instruction for that window, while the resource manager kept polling for one. + + - Retried: a scheduling job that failed on the server, an unreachable server, a 5xx response, and a bare HTTP 400 from a FlexMeasures older than v0.33.0 (its only way to report a failed job). + - Not retried: failures that would repeat on every attempt, such as an infeasible problem or a scheduler colliding with its own belief. + - A job that no worker picked up in time is checked once more. + - Giving up is logged as an error, saying that the storage is left without an instruction. + + [`#254 `__, `#273 `__] +* ``FRBCSimple`` takes an optional ``production_price_sensor_id``, for sites where feeding back is priced differently from drawing, such as under a feed-in tariff. Without it, ``price_sensor_id`` prices both directions, as before [`#261 `__] +* ``FRBCSimple`` takes ``relax_site_capacity``. With ``True``, a schedule may exceed the site's connection capacity at a price; use this where the capacity is a target set by others. With ``False``, the capacity is a hard limit. If unset, FlexMeasures decides, and since v1.0 it relaxes the capacity by default. So for a home whose connection is a physical limit, pass ``False`` [`#261 `__] +* An FRBC handler logs each instruction batch once, at debug level, listing the operation modes by name [`#261 `__] +* An FRBC handler tracks the instruction batch the resource manager is working from, and can revoke instructions it no longer wants carried out. Set its ``send_revocations`` attribute to ``True``. When a new batch replaces that one, the handler revokes the old batch's pending instructions: those without a reported status, or reported as ``NEW`` or ``ACCEPTED``. This is off by default, because a resource manager does not have to honour revocations [`#237 `__] +* New ``FRBC`` methods for handlers. ``filter_instruction_transitions()`` drops instructions that repeat the previous one. ``send_instruction_batch()`` sends a batch that replaces the previous one, unless a newer request has arrived in the meantime. ``system_description_fingerprint()`` tells a changed system description from a re-sent one [`#237 `__] +* ``ControlTypeHandler.send_message()``. Handlers now use it to send messages, instead of putting them on the CEM's sending queue themselves [`#237 `__] +* ``FRBCSimple`` takes an optional ``fill_rate_sensor_id``, and records the actuator's fill rate there. The fill rate is recorded in ``/s``, not as a power: S2 states a fill rate as fill level per second, which for a heat buffer is not electricity at all [`#236 `__, `#244 `__] +* ``flexmeasures_client.s2.clock``, with a ``WallClock`` and a ``SimulatedClock``, and ``CEM(clock=...)``. The clock sets the time for the CEM and for every handler it registers. That includes their timers, and the time a recorded state of charge is known from [`#264 `__, `#268 `__] +* ``CEM(accept_simulation_metadata=True)`` accepts messages from a co-simulation that are wrapped with the simulated time (``S2Wrapper``), and sets the CEM's clock from them. It is off by default, and then a wrapped message is refused as invalid. The example server turns it on with ``S2_ACCEPT_SIMULATION_METADATA`` [`#264 `__] +* ``flexmeasures_client.s2.site``, with ``FRBC_SITE_SENSORS``, ``ensure_site`` and ``FRBCSiteConfigurator``. It gives each resource manager its own FlexMeasures site, named after it, with an FRBC handler connected to that site's sensors [`#265 `__] +* ``CEM(site_configurator=...)`` sets up a resource manager's site once its details arrive. Until then, the CEM answers its messages with a temporary error. The example server enables this with ``S2_SITE_PER_RESOURCE_MANAGER`` [`#265 `__] +* ``examples/S2-co-simulation``: a FlexMeasures stack with one CEM per simulated home, for co-simulations whose resource managers connect over websockets [`#265 `__, `#267 `__] +* ``flexmeasures_client.s2.measurements.MeasurementRecorder`` and ``CEM(measurement_recorder=...)`` record a resource manager's measured power. Use one recorder per server, so that a resource manager that reconnects is not recorded twice [`#266 `__] Changed ------- -* Breaking for subclasses: ``FRBC.trigger_schedule()`` and ``FRBC.send_storage_status()`` each take a ``request_number`` keyword argument, the number of the planning request they answer, so a subclass that overrides either has to accept it [`#237 `__, `#240 `__] -* Breaking: in ``flexmeasures_client.s2.control_types.FRBC.utils``, ``compute_next_fill_level()`` is now ``compute_next_fill_level(fill_level, fill_rate, step, leakage_behaviour=None, usage_rate=0.0)``, with the step a ``timedelta`` and every rate per second, where it took ``(fill_level, fill_rate, leakage_behaviour, deltaT, usage)`` with ``deltaT`` in hours; and ``power_to_fill_rate_with_metrics()`` returns six values, the operation-mode factor added, instead of five, as do the tuples ``explain_choice()`` takes [`#256 `__, `#258 `__] -* ``fm_schedule_to_instructions()`` takes three new keyword arguments. ``fill_level_bounds`` are the fill levels its own running fill level is held between, so a caller that asked for a schedule against other bounds than the declared fill-level range is answered with the operation modes for the storage states the scheduler actually planned; without them the declared range is used, as before. ``leakage_behaviour`` and ``usage_forecast`` lower that running fill level as the storage leaks and is drawn from [`#245 `__, `#258 `__] -* An FRBC handler plans one schedule at a time, for the newest request only. In v0.9.0 every system description started a solve of its own, and every solve sent its own instruction batch, however many newer ones had arrived meanwhile. Now a request arriving while a schedule is planned waits for it, and is dropped where a newer one arrives meanwhile; a request out of date makes no further attempt after a failed one, is not turned into instructions, and sends no batch [`#237 `__, `#261 `__, `#269 `__] -* An FRBC handler asks for a schedule on every storage status, since the state of charge it reports is what a schedule starts from, and on a system description that arrives after the latest status and differs from the one before it, its validity included. So a resource manager is planned for whichever of the two it sends first, and whether or not its fill level moves. v0.9.0 planned on system descriptions only, every one of them, so a resource manager re-sending an unchanged description was planned for again each time: now such a description is acknowledged and otherwise ignored. A state of charge that cannot be recorded is logged, and planned from all the same [`#240 `__, `#259 `__, `#273 `__] -* ``FRBCSimple`` takes a ``resolution``, defaulting to fifteen minutes: the step a schedule is planned on and its start lined up with, and the step a fill-level target profile, a usage forecast and a storage efficiency are recorded on, and an actuator's power and fill rate are recorded as covering. A ``resolution`` or ``schedule_duration`` that is not positive is refused, and so is a ``schedule_duration`` that is not a whole number of ``resolution`` steps, since FlexMeasures answers with whole steps only. In v0.9.0, whatever the power sensor's resolution, a schedule was read as quarter hours, and an actuator's status recorded as covering one [`#240 `__, `#258 `__] -* A schedule starts at a resolution boundary: the one at or before the system description's ``valid_from`` plus ``valid_from_shift``, or, where that is in the past, the next boundary after now. ``valid_from_shift`` still defaults to one day. v0.9.0 started a schedule at ``valid_from`` plus the shift, unaligned, which left FlexMeasures without prices for a window off its steps. A boundary is found on the clock of the moment being floored, in the offset it carries, as FlexMeasures floors a series posted with it, and in the repeated hour of an autumn DST change it is the later boundary not after the moment [`#240 `__, `#259 `__] -* ``FRBCSimple`` asks for a schedule within the power ranges the actuator declares: a ``consumption-capacity`` and a ``production-capacity`` from the widest range it states, and, from a FlexMeasures at v1.0.0 or newer, which added the field, each distinct range as one of the ``operation-modes``, except where the ranges together cover exactly what the two capacities allow, without a gap, and so say nothing new while each costs the solver a binary per step. An older server is asked without the power bands, and the log says so, since a flex model naming a field the server does not know is refused outright. The scheduler then plans only power values the actuator can run at, instead of a fractional power that a resource manager has to round into a full-on block, overshooting the capacity its site is held to. An actuator that cannot produce at all is given a production capacity of zero, where before its storage could be planned as if it were a battery feeding the grid. Modes S2 reserves for abnormal conditions are left out of both, and are never instructed into, since a resource manager refuses an instruction into one that is not marked abnormal; an actuator with no other mode is not planned for, with a warning [`#248 `__, `#272 `__] -* ``FRBCSimple`` holds a storage to its declared fill-level range as a band whose breach is priced, rather than as hard bounds, and plans against what the resource manager declared about its storage. The band goes out with each request as ``soc-minima`` and ``soc-maxima`` over the whole window, with ``relax-soc-constraints`` in the flex context: the declared range, with the latest fill-level target profile, held inside the range, applying where it covers a step, and clipped at zero, since FlexMeasures takes no negative minima. A newer target profile replaces the one before it, as S2 means it to. It goes out with or without the band sensors, which only record it. The hard ``soc-min`` and ``soc-max`` are now zero and ``hard_bound_factor`` times the top of the range, widened to take in a state of charge above it, where v0.9.0 held the storage hard to the declared range: a state a storage really reaches, such as free-floating above the top of its range or being left below the bottom of it, was infeasible, and answered with no schedule at all, or by a fallback scheduler. A storage reported below zero is planned from zero, with a warning, since FlexMeasures refuses a negative ``soc-min``. Against a FlexMeasures older than v0.26.0, which cannot relax the band, the band is left out, with a note in the log, so the storage is planned anywhere between the wide hard bounds. The usage forecast is named as ``soc-usage`` where its sensor is given and the resource manager says it provides one, or has sent one regardless. A leakage behaviour is recorded as a ``storage-efficiency`` each time a schedule is asked for, over the window the schedule covers, from the element that applies at the fill level it starts from, as a share of that fill level or of the middle of the storage's range where it starts below that, and named only where FlexMeasures confirmed storing it. FlexMeasures takes a storage efficiency as a share of the stock where S2 states a rate, so the loss is exact only at that one fill level [`#244 `__, `#245 `__, `#259 `__, `#271 `__, `#273 `__] -* ``FRBCSimple`` posts a state of charge in its ``energy_unit``, ``kWh`` by default, and states the flex model's ``soc-unit`` to match (``kWh`` where ``energy_unit`` is neither ``kWh`` nor ``MWh``), where v0.9.0 labelled every state of charge, and the ``soc-unit`` of every schedule, ``MWh``, whatever unit the resource manager reported in: against the example server's ``kWh`` sensor, a fill level of 10 was recorded as 10,000. A resource manager that reports in megawatt hours now needs ``energy_unit="MWh"`` [`#244 `__] -* ``FRBCSimple`` records an actuator's power on the power sensor, and no longer writes anything to ``rm_discharge_sensor_id``, which stays a required argument. v0.9.0 posted minus the fill rate of the first-listed operation mode, labelled ``MWh``, onto that discharge sensor. Now it posts the power of the operation mode the actuator reports it is in, from the power range of the element that applies at the latest fill level, interpolated by the reported factor, as covering one ``resolution`` step. It is posted in the sign the power sensor records consumption in, read from the sensor's ``consumption_is_positive`` attribute (as S2 states it where the sensor cannot be read, with the reason logged), and converted into ``power_unit``. A second status for the same step supersedes the first rather than colliding with it, and a power that cannot be recorded is logged with its value, unit, sensor and moment [`#236 `__, `#252 `__, `#256 `__, `#258 `__, `#259 `__] -* ``FRBCSimple`` says so, as an error, where a schedule was planned by a fallback scheduler rather than the one it asked for. FlexMeasures answered an infeasible problem that way until v1.0.0 retired it, and an older server either serves that fallback job in place of the failed one or redirects to it, which this client follows, so either way the substitute arrives looking like the schedule that was asked for, though it is a coarse charging policy rather than a plan [`#254 `__] -* An FRBC handler refuses a system description of more than one actuator with ``INVALID_CONTENT`` and a diagnostic label, since every plan is made for one, where v0.9.0 acknowledged it and planned for the first actuator alone. A description whose actuator declares timers, or transitions that rule out a switch between its operation modes, is taken with a warning that they are not honoured; transitions allowing every switch, as an on/off device declaring both of its switches does, rule out nothing and are taken without one, and so is each timer the first time an ``FRBC.TimerStatus`` names it; every timer status is now kept [`#273 `__] -* The example resource manager is a heat pump keeping a hot-water buffer warm: it modulates from 400 to 1500 W at a COP of 4 while the buffer is cold and 3 once it is warm, declares its leakage, a day of hot-water use and a comfort profile, and simulates its buffer from S2's own definitions. It acknowledges each instruction, and reports the transition it asks for when the instruction's ``execution_time`` arrives, rather than printing it and carrying on; reports its fill level every ``S2_EXAMPLE_STATUS_INTERVAL`` seconds; follows only the latest plan, honouring the revocations the example server now sends, and tells the CEM where each instruction stands, so that only what it has not yet carried out is revoked; and sends its leakage, usage forecast and comfort profile again every ``S2_EXAMPLE_FORECAST_INTERVAL`` seconds. The example server records each declaration on a sensor of its own, creates its power sensor with ``consumption_is_positive``, as S2 states power, so that measured and scheduled power point the same way, seeds its demo prices from the current day and again every 12 hours, so that the window it plans for keeps its prices, and sets the site's graphs to show the buffer's fill level within its comfort band, and what fills and draws it [`#241 `__, `#252 `__, `#259 `__, `#262 `__] +* Breaking for subclasses: ``FRBC.trigger_schedule()`` and ``FRBC.send_storage_status()`` each take a ``request_number`` keyword argument: the number of the planning request they answer. A subclass that overrides either method has to accept it [`#237 `__, `#240 `__] +* Breaking: two functions in ``flexmeasures_client.s2.control_types.FRBC.utils`` changed signature. + + - ``compute_next_fill_level()`` is now ``compute_next_fill_level(fill_level, fill_rate, step, leakage_behaviour=None, usage_rate=0.0)``. The step is a ``timedelta``, and every rate is per second. Before, it was ``(fill_level, fill_rate, leakage_behaviour, deltaT, usage)``, with ``deltaT`` in hours. + - ``power_to_fill_rate_with_metrics()`` returns six values instead of five; the operation-mode factor is new. The tuples that ``explain_choice()`` takes changed the same way. + + [`#256 `__, `#258 `__] +* ``fm_schedule_to_instructions()`` takes three new keyword arguments. ``fill_level_bounds`` are the bounds of the fill level it tracks while translating. Pass them if the schedule was requested with bounds other than the declared fill-level range, so that each step gets the operation mode for the state the scheduler actually planned. Without them, the declared range is used, as before. ``leakage_behaviour`` and ``usage_forecast`` lower the tracked fill level as the storage leaks and is used [`#245 `__, `#258 `__] +* An FRBC handler plans one schedule at a time, and only for the newest request. In v0.9.0, every system description started its own solve, and every solve sent its own instruction batch, even when newer ones had arrived. Now a request that arrives during planning waits. It is dropped if an even newer request arrives. A request that is out of date is not retried after a failure, not turned into instructions, and sends no batch [`#237 `__, `#261 `__, `#269 `__] +* An FRBC handler requests a schedule on every storage status, because a schedule starts from the reported state of charge. It also requests one on a system description that arrives after the latest status and differs from the previous one, including in its validity. So a resource manager gets a plan for whichever of the two it sends first, whether or not its fill level changes. In v0.9.0, only system descriptions triggered planning, every one of them, so re-sending an unchanged description triggered a new plan each time. Now such a description is acknowledged and otherwise ignored. If the state of charge cannot be recorded, this is logged, and the schedule is still planned from it [`#240 `__, `#259 `__, `#273 `__] +* ``FRBCSimple`` takes a ``resolution`` (default 15 minutes). It is the step a schedule is planned on and aligned to. Fill-level target profiles, usage forecasts and storage efficiencies are recorded on this step, and each recorded actuator power and fill rate covers one step. A ``resolution`` or ``schedule_duration`` that is not positive is refused. So is a ``schedule_duration`` that is not a whole number of steps, because FlexMeasures only answers with whole steps. In v0.9.0, a schedule was always read as quarter hours, whatever the power sensor's resolution, and an actuator status was recorded as covering a quarter hour [`#240 `__, `#258 `__] +* A schedule starts on a resolution boundary: the last one at or before the system description's ``valid_from`` plus ``valid_from_shift``, or, if that has passed, the next one after now. ``valid_from_shift`` still defaults to one day. In v0.9.0, a schedule started exactly at ``valid_from`` plus the shift. That moment could lie between steps, which left FlexMeasures without prices for the window. Boundaries are computed in the UTC offset of the moment itself, as FlexMeasures does for a series posted with that offset. In the repeated hour at the end of daylight saving time, the later boundary that is not after the moment is used [`#240 `__, `#259 `__] +* ``FRBCSimple`` keeps a schedule within the power ranges the actuator declares, so the scheduler only plans powers the actuator can run at. Before, it could plan a fractional power, which the resource manager had to round up to a full-on block, exceeding the site's capacity. + + - The widest declared range sets ``consumption-capacity`` and ``production-capacity``. An actuator that cannot produce gets a production capacity of zero; before, its storage could be planned as if it were a battery feeding the grid. + - Each distinct range is also sent as one of the ``operation-modes``, if the server runs FlexMeasures v1.0.0 or newer (the release that added this field). This is skipped if the ranges together cover exactly what the capacities allow, without gaps: then they add nothing, and each one would cost the solver a binary variable per step. An older server is asked without the operation modes, and this is logged, because it would refuse a flex model with a field it does not know. + - Modes that S2 reserves for abnormal conditions are left out of both the capacities and the operation modes, and are never used in instructions: a resource manager refuses an instruction into such a mode if the instruction is not marked as abnormal. An actuator that only has such modes is not planned for, with a warning. + + [`#248 `__, `#272 `__] +* ``FRBCSimple`` treats the declared fill-level range as a soft band whose breach is priced, instead of as hard bounds. It also plans with what the resource manager declared about its storage. + + - The band is sent with every schedule request as ``soc-minima`` and ``soc-maxima`` over the whole window, with ``relax-soc-constraints`` in the flex context. It is the declared range, narrowed by the latest fill-level target profile wherever that profile covers a step, and clipped at zero, because FlexMeasures accepts no negative minima. A newer target profile replaces the previous one, as S2 intends. The band is sent whether or not the band sensors are given; they only record it. + - The hard bounds ``soc-min`` and ``soc-max`` are now zero and ``hard_bound_factor`` times the top of the range, raised to include a state of charge above it. In v0.9.0, the declared range itself was the hard bound. So a storage that really was outside it, for example floating above the top or left below the bottom, made the problem infeasible: there was no schedule at all, or one from a fallback scheduler. A storage reported below zero is planned from zero, with a warning, because FlexMeasures refuses a negative ``soc-min``. + - FlexMeasures older than v0.26.0 cannot relax the band. Then the band is left out, and this is logged, so the storage is planned anywhere between the wide hard bounds. + - The usage forecast is sent as ``soc-usage`` if its sensor is given and the resource manager provides a forecast (or has sent one anyway). + - The leakage behaviour is recorded as a ``storage-efficiency`` with each schedule request, over the window of the schedule. It uses the leakage at the starting fill level, as a share of that fill level (or of the middle of the range, if the storage starts below that). It is only named in the flex model if FlexMeasures confirmed storing it. FlexMeasures takes a storage efficiency as a share of the stock, whereas S2 states a rate, so the loss is exact only at that one fill level. + + [`#244 `__, `#245 `__, `#259 `__, `#271 `__, `#273 `__] +* ``FRBCSimple`` posts the state of charge in its ``energy_unit`` (``kWh`` by default), and sets the flex model's ``soc-unit`` to match (to ``kWh`` if ``energy_unit`` is neither ``kWh`` nor ``MWh``). In v0.9.0, every state of charge and every schedule's ``soc-unit`` was labelled ``MWh``, whatever unit the resource manager reported in. On the example server's ``kWh`` sensor, a fill level of 10 was recorded as 10,000. A resource manager that reports in megawatt hours now needs ``energy_unit="MWh"`` [`#244 `__] +* ``FRBCSimple`` records the actuator's power on the power sensor. It no longer writes to ``rm_discharge_sensor_id``, which is still a required argument. In v0.9.0, it posted the negative fill rate of the first listed operation mode, labelled ``MWh``, to that discharge sensor. + + - The power is now that of the operation mode the actuator reports being in: taken from the power range that applies at the latest fill level, interpolated by the reported factor, and posted as covering one ``resolution`` step. + - The sign follows the power sensor's ``consumption_is_positive`` attribute. If the sensor cannot be read, S2's sign is used, and the reason is logged. The power is converted into ``power_unit``. + - A second status for the same step replaces the first, instead of colliding with it. If the power cannot be recorded, the log gives its value, unit, sensor and time. + + [`#236 `__, `#252 `__, `#256 `__, `#258 `__, `#259 `__] +* ``FRBCSimple`` logs an error if a schedule was made by a fallback scheduler instead of the requested one. Until v1.0.0, FlexMeasures answered an infeasible problem that way. An older server either serves the fallback job in place of the failed one, or redirects to it, which this client follows. Either way, the substitute looks like the requested schedule, but it is only a coarse charging policy, not a plan [`#254 `__] +* An FRBC handler refuses a system description with more than one actuator, answering ``INVALID_CONTENT`` with a diagnostic label, because every plan is made for one actuator. In v0.9.0, it accepted such a description and planned for the first actuator only. + + - An actuator that declares timers, or transitions that rule out switching between some of its operation modes, is accepted with a warning that these are not honoured. Transitions that allow every switch rule nothing out, so they cause no warning; an on/off device that declares both its switches is an example. + - The first ``FRBC.TimerStatus`` for each timer also causes the warning. Every timer status is now stored. + + [`#273 `__] +* The example resource manager is now a heat pump that keeps a hot-water buffer warm. + + - It modulates from 400 to 1500 W, with a COP of 4 while the buffer is cold and 3 once it is warm. It declares its leakage, a day of hot-water use and a comfort profile, and simulates its buffer using S2's own definitions. + - It acknowledges each instruction, and reports the transition it asks for when the instruction's ``execution_time`` arrives, instead of printing it and moving on. It reports its fill level every ``S2_EXAMPLE_STATUS_INTERVAL`` seconds. + - It follows only the latest plan, and honours the revocations the example server now sends. It tells the CEM the status of each instruction, so that only instructions not yet carried out are revoked. + - It re-sends its leakage, usage forecast and comfort profile every ``S2_EXAMPLE_FORECAST_INTERVAL`` seconds. + + The example server records each declaration on its own sensor. It creates its power sensor with ``consumption_is_positive``, as S2 states power, so that measured and scheduled power have the same sign. It seeds its demo prices from the current day, and again every 12 hours, so that the window it plans for keeps its prices. It sets up the site's graphs to show the buffer's fill level within its comfort band, and what fills and drains it [`#241 `__, `#252 `__, `#259 `__, `#262 `__] Removed ------- -* ``flexmeasures_client.s2.cem.get_commodity_unit``, which stated power in kW where S2 states it in W [`#266 `__] -* ``op_mode_compute_factor``, which worked a factor back out of a fill rate and returned zero wherever an element's fill rate is one value; the factor now comes with the operation mode chosen, from its power range [`#258 `__] -* ``flexmeasures_client.s2.utils.get_validation_error_summary``, which iterated a pydantic ``ValidationError``, which is not iterable, so it raised on any error it was given [`#257 `__] +* ``flexmeasures_client.s2.cem.get_commodity_unit``. It stated power in kW, while S2 states it in W [`#266 `__] +* ``op_mode_compute_factor``. It derived a factor from a fill rate, and returned zero wherever an element has a single fill rate. The factor now comes with the chosen operation mode, from its power range [`#258 `__] +* ``flexmeasures_client.s2.utils.get_validation_error_summary``. It iterated over a pydantic ``ValidationError``, which is not iterable, so it raised on every error [`#257 `__] Fixed ----- -* A full message history keeps the newest messages: it made room by evicting the newest entry, so it kept a session's first messages and lost every later one but the last. A message set again under the same id becomes the latest [`#274 `__] -* ``Handler.is_revoked()`` finds an object the resource manager revoked. The CEM and its handlers stored a revoked object's id as a ``UUID``, so a lookup by its string id always missed [`#257 `__] -* An FRBC actuator status that arrives before any system description, as one can after a reconnection, is logged and skipped, rather than raising in a background task [`#274 `__] -* A CEM records measured power by the moment it was measured, as one average per step of its sensor's resolution (15 minutes on the example's sites, where it was five), once the step is over by its clock, with the first measurement after that, and in watts, as S2 states it. It binned measurements by its own now, which dropped every measurement a resource manager sent after the fact, as a co-simulation's does, labelled watts as kilowatts, and kept its samples in a buffer shared by every CEM in the process. Recording happens in the background, retried a few times where it fails [`#266 `__] -* A CEM without a sensor for a resource manager's measured power no longer records an L1 measurement on sensor 357, which was whichever sensor of whichever site on the server had that id, and answered it as recorded [`#265 `__] -* A message of the control type a CEM is selecting, arriving before the resource manager confirmed the selection, is taken as that confirmation, since a resource manager sends a control type's messages only once it works in it. A CEM used to stay without a control type, answering each such message with a temporary error that the resource manager retried, as a co-simulation's reconnecting resource managers showed. A message of any other control type activates nothing [`#263 `__] -* Closing a control-type handler cancels the work it still has in flight, recording included, and the example server's session ends with its connection, where a session used to keep working after its resource manager had disconnected [`#263 `__] -* A control-type handler can log before it is registered, and one without its own ``close()`` no longer breaks CEM teardown [`#236 `__] -* An ``FRBCSimple`` handler accepts an ``FRBC.FillLevelTargetProfile``, an ``FRBC.UsageForecast`` and an ``FRBC.LeakageBehaviour`` instead of raising ``NotImplementedError`` for each. The CEM dispatches these as tasks nobody awaits, so every connection by a resource manager that sends all three logged three "Task exception was never retrieved" tracebacks [`#244 `__] -* ``FRBCSimple`` asks for a schedule in watts, which is what S2 states an operation mode's power ranges in, rather than in whatever unit the power sensor happens to be recorded in. The example server creates its power sensor in ``kW``, so every scheduled power was read as a thousandth of itself: it then sat nearest the operation mode that draws nothing, and the resource manager was instructed to idle through a schedule that asked it to run [`#247 `__] -* On a state-of-charge sensor that records instants, a state of charge is recorded as a reading at an instant, where it was recorded over the minute after it, which FlexMeasures took for a forecast. On a sensor with a resolution, which refuses an instant, it is still recorded over a minute [`#259 `__] -* Translating a schedule into instructions takes every rate per second, as S2 states it. It advanced its running fill level by a fill rate per second times a step in hours, so the level moved 3600 times too little, and every step of a schedule was answered with the operation mode for the fill level the schedule started from. Leakage now lowers that level, from the element of the latest ``FRBC.LeakageBehaviour`` that applies, where it used to be read from two fields S2 does not have and so never counted, and so does usage, from the latest ``FRBC.UsageForecast``. Each step is followed exactly, as a resource manager follows an instruction: a fill level crossing into another element, or a change in usage within a step, is taken at the moment it happens, and a level that reaches a boundary its far side would push it back from stays at that boundary [`#258 `__] -* Translating a schedule into instructions no longer depends on the order a mode lists its elements in: where two elements share a boundary, the one that applies answers, not the first listed [`#256 `__] -* A schedule is read at the resolution FlexMeasures answers in, its duration over its number of values, rather than as quarter hours whatever it was, which made a schedule for an hourly or a five-minute power sensor raise, so that no instruction went out [`#258 `__] -* A new instruction goes out wherever the operation mode or its factor changes, including at a constant power where the fill level passes into another element of the mode, and only there, rather than wherever the power changes. A difference in the factor of a millionth or less, which is the noise of converting a schedule from MW to W, is not a new instruction [`#237 `__, `#258 `__] -* An instruction asks for the factor that draws the power the schedule asks for, taken from the operation mode's power range. It was worked back out of the fill rate, which asked for the bottom of the power range wherever the fill rate is one value and the power a range [`#258 `__] -* ``leakage_behaviour_to_storage_efficiency()`` compounds a leakage rate per second, as S2 states it, where it compounded it per hour, which declared a buffer losing about 7% of its heat over twelve hours as holding it to within 0.002%. It takes the rate of the element that applies at a new ``fill_level`` keyword argument, or of the leakiest element where none is given, where it took the last one listed, which S2 does not require to be the top one; and a new ``share_of`` keyword argument sets the fill level the loss is a share of, which is otherwise the top of the highest element's range [`#256 `__, `#258 `__, `#259 `__] -* ``FillRateBasedControlTUNES`` posts its usage forecast per hour, which is what a value labelled MW means to FlexMeasures, where it posted a quarter-hour amount that FlexMeasures planned as a quarter of the real draw. It reads it back in the resource manager's own units, per second, dividing out the fill-level scale it was posted with, and plans its instructions with its latest leakage behaviour [`#258 `__] -* ``FillRateBasedControlTUNES`` plans, and records its conversion efficiencies, when an operation mode has no diagnostic label, which S2 makes optional. It looked for ``THP`` or ``NES`` inside a label that may be absent, so one unlabelled mode stopped planning, and the recording of the efficiencies of every mode listed after it [`#257 `__] -* ``FillRateBasedControlTUNES`` records an ``FRBC.LeakageBehaviour`` instead of answering it with ``PERMANENT_ERROR``. It read its fill-level scale from a misspelt attribute, and the error that raised was taken for a failed post, so no leakage behaviour was ever recorded [`#255 `__] +* A full message history now keeps the newest messages. It used to make room by evicting the newest entry, so it kept a session's first messages and lost all later ones except the last. A message stored again under the same id now counts as the latest [`#274 `__] +* ``Handler.is_revoked()`` now finds objects the resource manager revoked. The CEM and its handlers stored a revoked object's id as a ``UUID``, so a lookup by its string id always missed [`#257 `__] +* An FRBC actuator status that arrives before any system description is logged and skipped, instead of raising an error in a background task. This can happen after a reconnection [`#274 `__] +* A CEM records measured power by the time it was measured. It records one average per step of the sensor's resolution (15 minutes on the example's sites, previously five), in watts, as S2 states it. A step is recorded with the first measurement that arrives after the step has ended by the CEM's clock. Before, measurements were grouped by the CEM's current time, which dropped every measurement sent after the fact, as co-simulations do. They were also labelled as kilowatts instead of watts, and kept in a buffer shared by every CEM in the process. Recording happens in the background, with a few retries if it fails [`#266 `__] +* A CEM without a sensor for a resource manager's measured power no longer records L1 measurements on sensor 357. That was whatever sensor had that id on the server, on any site, and the CEM reported the measurement as recorded [`#265 `__] +* A CEM that is selecting a control type now takes a message of that control type as confirmation of the selection, because a resource manager only sends such messages once it uses that control type. Before, the CEM stayed without a control type, and answered each such message with a temporary error, which the resource manager retried; co-simulations whose resource managers reconnect showed this. A message of any other control type activates nothing [`#263 `__] +* Closing a control-type handler cancels its remaining work, including recording. The example server's session now ends when its connection closes; it used to keep working after the resource manager disconnected [`#263 `__] +* A control-type handler can log before it is registered, and a handler without its own ``close()`` no longer breaks the CEM's teardown [`#236 `__] +* An ``FRBCSimple`` handler now accepts ``FRBC.FillLevelTargetProfile``, ``FRBC.UsageForecast`` and ``FRBC.LeakageBehaviour`` messages, instead of raising ``NotImplementedError`` for each. The CEM runs these as tasks that nothing awaits, so a resource manager sending all three caused three "Task exception was never retrieved" tracebacks on every connection [`#244 `__] +* ``FRBCSimple`` requests schedules in watts, the unit of S2 operation-mode power ranges, instead of in the power sensor's unit. The example server's power sensor is in ``kW``, so every scheduled power was read as a thousandth of its value. It then matched the operation mode that draws nothing, and the resource manager was told to idle while the schedule asked it to run [`#247 `__] +* On a state-of-charge sensor that records instants, a state of charge is now recorded as a reading at an instant. Before, it was recorded as covering the minute after it, which FlexMeasures took for a forecast. On a sensor with a resolution, which refuses instants, it is still recorded over one minute [`#259 `__] +* Translating a schedule into instructions now treats every rate as per second, as S2 states it. + + - It used to advance the tracked fill level by a fill rate per second times a step in hours, so the level moved 3600 times too little. As a result, every step got the operation mode for the fill level the schedule started from. + - Leakage now lowers the tracked fill level, using the element of the latest ``FRBC.LeakageBehaviour`` that applies. Before, leakage was read from two fields that S2 does not have, so it was never counted. Usage from the latest ``FRBC.UsageForecast`` now lowers it too. + - Each step is followed exactly, as a resource manager follows an instruction. If the fill level crosses into another element, or usage changes within a step, that is taken into account at the moment it happens. A level that reaches a boundary, beyond which it would be pushed back, stays at that boundary. + + [`#258 `__] +* Translating a schedule into instructions no longer depends on the order in which a mode lists its elements. Where two elements share a boundary, the one that applies is used, not the first one listed [`#256 `__] +* A schedule is read at the resolution FlexMeasures answers in: its duration divided by its number of values. It used to be read as quarter hours, so a schedule for an hourly or a five-minute power sensor raised an error, and no instruction was sent [`#258 `__] +* A new instruction is sent whenever the operation mode or its factor changes, and only then, instead of whenever the power changes. This includes a constant power at which the fill level moves into another element of the mode. A change in the factor of a millionth or less does not count: that is rounding noise from converting a schedule from MW to W [`#237 `__, `#258 `__] +* An instruction now asks for the factor that draws the scheduled power, taken from the operation mode's power range. It used to derive the factor from the fill rate, which asked for the bottom of the power range wherever the fill rate is a single value and the power a range [`#258 `__] +* ``leakage_behaviour_to_storage_efficiency()`` compounds a leakage rate per second, as S2 states it. It compounded it per hour, so a buffer that loses about 7% of its heat over twelve hours was described as losing 0.002%. + + - A new ``fill_level`` keyword argument selects the element that applies at that fill level. Without it, the leakiest element is used. Before, it used the last element listed, which S2 does not require to be the top one. + - A new ``share_of`` keyword argument sets the fill level that the loss is a share of. Without it, that is the top of the highest element's range. + + [`#256 `__, `#258 `__, `#259 `__] +* ``FillRateBasedControlTUNES`` posts its usage forecast per hour, which is what a value in MW means to FlexMeasures. It used to post a quarter-hour amount, which FlexMeasures planned as a quarter of the real usage. It reads the forecast back in the resource manager's own units, per second, dividing out the fill-level scale it was posted with, and plans its instructions with its latest leakage behaviour [`#258 `__] +* ``FillRateBasedControlTUNES`` now plans, and records its conversion efficiencies, when an operation mode has no diagnostic label, which S2 makes optional. It looked for ``THP`` or ``NES`` in a label that may be missing, so a single unlabelled mode stopped planning, and stopped the efficiencies of all modes listed after it from being recorded [`#257 `__] +* ``FillRateBasedControlTUNES`` records an ``FRBC.LeakageBehaviour`` instead of answering it with ``PERMANENT_ERROR``. It read its fill-level scale from a misspelt attribute, and the resulting error was mistaken for a failed post, so no leakage behaviour was ever recorded [`#255 `__] * Importing either S2 example script no longer runs it, so they can be read, imported and tested like any other module [`#252 `__] [`issue #251 `__] -* The example CEM server answers an S2 handshake at once, instead of after configuring its site with FlexMeasures, which took longer than the timeout a resource manager waits for that answer [`#241 `__] +* The example CEM server answers an S2 handshake immediately. It used to configure its site in FlexMeasures first, which took longer than a resource manager waits for the answer [`#241 `__] v0.9.0 | 2026-04-21 =================== diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 66b6e9a8..f93b4948 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,20 +21,20 @@ Added * ``server_version_at_least``, which answers whether the server is of a given version or newer, looking its version up where that is not known yet, for a caller that sends a field or calls an endpoint only where the server has it. Release generations are compared, so a development build such as ``0.33.0.dev26`` counts as ``0.33.0`` [`#248 `__] * ``ingestion_polling_interval``, ``ingestion_polling_max_interval`` and ``ingestion_polling_timeout``, which govern how the ingestion of posted sensor data is waited for, on a tighter profile than the ``job_polling_*`` settings give other background jobs [`#242 `__] -* ``post_sensor_data`` takes ``confirm_ingestion``, which raises where the ingestion of the posted data could not be confirmed, rather than logging it and carrying on, for a caller that remembers having recorded the data and would otherwise never record it again [`#273 `__] +* ``post_sensor_data`` takes ``confirm_ingestion``. With ``True``, it raises an error if it cannot confirm that FlexMeasures ingested the posted data, instead of logging this and carrying on. Use it if your code remembers what it has recorded, and would otherwise never record the data again [`#273 `__] * Optional ``rate_limit_notifier`` and ``job_status_notifier`` callbacks, to report rate limits and the progress of jobs being waited for, instead of logging them [`#225 `__] * ``get_automations``, ``get_automation``, ``add_automation``, ``update_automation``, ``delete_automation``, ``trigger_automation`` and ``trigger_and_await_automation``, to manage and run automations through the API on FlexMeasures v1.1.0 and up [`#230 `__] Changed ------- -* ``add_asset`` no longer requires a ``latitude`` and ``longitude``, which FlexMeasures does not require either, for an asset with no location, such as one standing for a community. ``generic_asset_type_id`` stays required, and half a location is refused [`#261 `__] +* ``add_asset`` no longer requires a ``latitude`` and ``longitude``, just like FlexMeasures. This allows assets without a location, such as one that represents a community. ``generic_asset_type_id`` is still required, and a latitude without a longitude (or the other way around) is refused [`#261 `__] * ``post_sensor_data``, and the deprecated ``post_measurements`` it was renamed from, wait for a server of FlexMeasures 0.33.0 or above that ingests the posted data asynchronously, answering ``202 Accepted`` with a job id, to finish doing so, so that a caller can read the data back or schedule over it; a failed job raises ``JobFailedError``, while a job still pending at the timeout, or one that cannot be looked up, is logged as an error and leaves the post successful. Pass ``await_ingestion=False`` to return as soon as the data is accepted [`#242 `__] Removed ------- -* ``response_handling.check_content_type``, which refused an answer in a JSON variant that aiohttp parses, such as ``application/problem+json``, with an error that raised an ``AttributeError`` of its own when printed. Such an answer is now read like any other. A content type that is not JSON at all is still reported as a ``ConnectionError``, by aiohttp's own check [`#257 `__] +* ``response_handling.check_content_type``. It refused responses in JSON variants that aiohttp can parse, such as ``application/problem+json``, and the error it raised caused an ``AttributeError`` of its own when printed. Such responses are now read like any other. A response that is not JSON at all is still reported as a ``ConnectionError``, by aiohttp's own check [`#257 `__] Fixed -----