JSON Profile Spec
profile_json.d.ts
/** Copyright © 2023 Jeff Kletsky. All Rights Reserved.
*
* License for this software, part of the pyDE1 package, is granted under
* GNU General Public License v3.0 only
* SPDX-License-Identifier: GPL-3.0-only
*/
export const VERSION = '2.1';
export const SPEC_REVISION = '2.1';
/** This file has a primary purpose of documenting the pyDE1 profile format
*
* Credit and appreciation to Mimoja for the initial implementation
* of JSON profiles in the de1app (TCL).
*
* This definition removes most redundant and contextually meaningless
* information in the TCL representation of Buckman. Buckman's implementation
* tied the data structure and naming to the UI implementation in de1app.
* This implementation retains some legacy fields that de1app apparently needs
* to be able to select an editor for a profile. It also extends
* Mimoja's implementation with additional metadata related to
* the source of the profile and how and when it was created.
*
* Implementations MAY be tolerant of the presence of extra fields
* that may have been written by other tools.
*
* TypeScript was selected as a reasonable documentation format.
* This document has not been validated in a TypeScript project.
*
* Limitations on the range of numeric values is not specified here.
*
* Please report any issues or points that need clarification.
*/
/** NB: TCL does not discriminate between the number 5.3 and the string "5.3".
* As a result, JSON written by de1app writes a string rather than a number.
*
* Implementations that read de1app-generated JSON profiles MAY be robust
* to this representation. However, any JSON written SHOULD be compliant
* with JSON standards and represent numbers as numbers, not strings.
*/
export type NonEmptyArray<T> = [T, ...T[]];
export type PumpType = 'pressure' | 'flow';
export type MoveOnType = 'seconds' | 'volume' | 'weight';
export type ExitType = 'pressure' | 'flow';
export type ExitCondition = 'over' | 'under';
export type TransitionType = 'fast' | 'smooth';
export type TemperatureSensor = 'coffee' | 'water';
/** A loose labeling of the general category of the intent of the profile.
* Potentially can be extended by users. Primarily used for skipping upload
* to Visualizer and for categorization in DYE, Visualizer, and others. */
export type BeverageType =
'espresso'
| 'calibrate'
| 'cleaning'
| 'manual'
| 'pourover'
| 'tea_portafilter';
/** Buckman tied profile type to the name of the UI screen
* on which it was edited. See also type ProfileEditor */
export type LegacyProfileType =
'settings_2a'
| 'settings_2b'
| 'settings_2c'
| 'settings_2c2';
/** A normalization of LegacyProfileType.
* Does not impact how the DE1 operates. */
export type ProfileEditor = 'advanced' | 'flow' | 'pressure';
export type ISOTimestamp = string
export type SemanticVersion = string
export interface ProfileJSON {
/** Identifies the semantic version of the JSON format, presently 2.1 */
version: SemanticVersion;
/** A one-line string that can identify the profile to a user */
title: string;
/** A longer description of the profile and/or its use
* that can be multi-line */
notes: string;
/** The author of the profile.
* NB: "Decent" is likely inappropriately present
* for user-generated profiles */
author: string;
/** A general category for the beverage or function.
* Used by DYE, Visualizer uploaders, and others */
beverage_type: BeverageType;
/** An array of one or more steps or frames describing the actions
* the DE1 should take */
steps: NonEmptyArray<ProfileStep>
/** If non-zero and non-null, the estimated volume
* at which the DE1 should stop */
target_volume: number | null;
/** If non-zero and non-null, the weight
* at which the DE1 should be stopped */
target_weight: number | null;
/** An integer indicating the zero-based frame number after which to start
* the "pour" accounting of time and volume */
target_volume_count_start: number;
/** If non-zero, the target temperature in °C to which the tank
* should be heated prior to starting the frames. */
tank_temperature: number;
/** Legacy field from de1app profiles, seemingly all contain "en" */
lang: string;
/** Legacy identifier in the de1app */
legacy_profile_type: LegacyProfileType;
/** Legacy style of de1app editor to display or edit the profile */
type: ProfileEditor;
/** A reference to the source of the profile.
* The field is present but often the empty string from de1app */
reference_file: string;
/** An optional descriptor of how and when this version was generated */
creator?: CreatorData;
}
export type ProfileStep = ProfileStepPressure | ProfileStepFlow;
export interface ProfileStepBase {
/** A label suitable for rendering in a UI */
name: string;
/** How the target over time should move from the controlled variable
* at the start of the frame (at run time) to the target for this frame */
transition: TransitionType;
/** An optional exit condition based on flow or pressure */
exit?: StepExitConditition;
/** The volume in mL dispensed in this fram over which
* the frame would be exited, */
volume: number;
/** The duration of this frame over which the frame would be exited */
seconds: number;
/** If present, the weight in g over which the frame would be exited */
weight?: number;
/** The target temperature in °C for this frame. See also sensor: */
temperature: number;
/** The sensor to use to measure temperature for this frame */
sensor: TemperatureSensor;
}
export interface ProfileStepPressure extends ProfileStepBase {
/** Defines this as a pressure-driven step */
pump: 'pressure';
/** The target pressure in bar */
pressure: number;
}
export interface ProfileStepFlow extends ProfileStepBase {
/** Defines this as a flow-driven step */
pump: 'flow';
/** The target flow in mL/s */
flow: number;
}
export interface StepExitConditition {
/** Exit based on flow or pressure. Omit StepExitCondition otherwise */
type: ExitType;
/** Is the exit to occur when the measured value crosses the threshold
* from above or from below. */
condition: ExitCondition;
/** The numeric threshold */
value: number;
}
/** See current DE1 documentation on how the Limiter parameters impact operation.
* At least at this time, pressure-driven and flow-driven profiles
* behave differently. The description is the same in the profile for both. */
export interface Limiter {
value: number;
range: number
}
export interface CreatorData {
/** A reference to the product name or utility */
name: string;
/** An identifier string of the version of the product or utility
* Although semantic versioning is preferred, it is not required. */
version: string;
/** An ISO timestamp of when the conversion was performed.
* Should include full date, time with at least seconds, and timezone */
timestamp: ISOTimestamp;
}