<|start_filename|>ui/src/proto/pkg/espressopb/espresso_pb_service.js<|end_filename|>
/* eslint-disable */
// package: espressopb
// file: pkg/espressopb/espresso.proto

var pkg_espressopb_espresso_pb = require("../../pkg/espressopb/espresso_pb");
var grpc = require("@improbable-eng/grpc-web").grpc;

var Espresso = (function () {
  function Espresso() {}
  Espresso.serviceName = "espressopb.Espresso";
  return Espresso;
}());

Espresso.BoilerTemperature = {
  methodName: "BoilerTemperature",
  service: Espresso,
  requestStream: false,
  responseStream: true,
  requestType: pkg_espressopb_espresso_pb.TemperatureStreamRequest,
  responseType: pkg_espressopb_espresso_pb.TemperatureStreamResponse
};

Espresso.GetConfiguration = {
  methodName: "GetConfiguration",
  service: Espresso,
  requestStream: false,
  responseStream: false,
  requestType: pkg_espressopb_espresso_pb.GetConfigurationRequest,
  responseType: pkg_espressopb_espresso_pb.Configuration
};

Espresso.SetConfiguration = {
  methodName: "SetConfiguration",
  service: Espresso,
  requestStream: false,
  responseStream: false,
  requestType: pkg_espressopb_espresso_pb.Configuration,
  responseType: pkg_espressopb_espresso_pb.Configuration
};

exports.Espresso = Espresso;

function EspressoClient(serviceHost, options) {
  this.serviceHost = serviceHost;
  this.options = options || {};
}

EspressoClient.prototype.boilerTemperature = function boilerTemperature(requestMessage, metadata) {
  var listeners = {
    data: [],
    end: [],
    status: []
  };
  var client = grpc.invoke(Espresso.BoilerTemperature, {
    request: requestMessage,
    host: this.serviceHost,
    metadata: metadata,
    transport: this.options.transport,
    debug: this.options.debug,
    onMessage: function (responseMessage) {
      listeners.data.forEach(function (handler) {
        handler(responseMessage);
      });
    },
    onEnd: function (status, statusMessage, trailers) {
      listeners.status.forEach(function (handler) {
        handler({ code: status, details: statusMessage, metadata: trailers });
      });
      listeners.end.forEach(function (handler) {
        handler({ code: status, details: statusMessage, metadata: trailers });
      });
      listeners = null;
    }
  });
  return {
    on: function (type, handler) {
      listeners[type].push(handler);
      return this;
    },
    cancel: function () {
      listeners = null;
      client.close();
    }
  };
};

EspressoClient.prototype.getConfiguration = function getConfiguration(requestMessage, metadata, callback) {
  if (arguments.length === 2) {
    callback = arguments[1];
  }
  var client = grpc.unary(Espresso.GetConfiguration, {
    request: requestMessage,
    host: this.serviceHost,
    metadata: metadata,
    transport: this.options.transport,
    debug: this.options.debug,
    onEnd: function (response) {
      if (callback) {
        if (response.status !== grpc.Code.OK) {
          var err = new Error(response.statusMessage);
          err.code = response.status;
          err.metadata = response.trailers;
          callback(err, null);
        } else {
          callback(null, response.message);
        }
      }
    }
  });
  return {
    cancel: function () {
      callback = null;
      client.close();
    }
  };
};

EspressoClient.prototype.setConfiguration = function setConfiguration(requestMessage, metadata, callback) {
  if (arguments.length === 2) {
    callback = arguments[1];
  }
  var client = grpc.unary(Espresso.SetConfiguration, {
    request: requestMessage,
    host: this.serviceHost,
    metadata: metadata,
    transport: this.options.transport,
    debug: this.options.debug,
    onEnd: function (response) {
      if (callback) {
        if (response.status !== grpc.Code.OK) {
          var err = new Error(response.statusMessage);
          err.code = response.status;
          err.metadata = response.trailers;
          callback(err, null);
        } else {
          callback(null, response.message);
        }
      }
    }
  });
  return {
    cancel: function () {
      callback = null;
      client.close();
    }
  };
};

exports.EspressoClient = EspressoClient;



<|start_filename|>ui/ui.go<|end_filename|>
package ui

import "embed"

//go:embed build
// Build contains the content of the build directory
var Build embed.FS


<|start_filename|>ui/src/proto/pkg/espressopb/espresso_pb.js<|end_filename|>
/* eslint-disable */
// source: pkg/espressopb/espresso.proto
/**
 * @fileoverview
 * @enhanceable
 * @suppress {messageConventions} JS Compiler reports an error if a variable or
 *     field starts with 'MSG_' and isn't a translatable message.
 * @public
 */
// GENERATED CODE -- DO NOT EDIT!

var jspb = require('google-protobuf');
var goog = jspb;
var global = Function('return this')();

var google_protobuf_timestamp_pb = require('google-protobuf/google/protobuf/timestamp_pb.js');
goog.object.extend(proto, google_protobuf_timestamp_pb);
goog.exportSymbol('proto.espressopb.Configuration', null, global);
goog.exportSymbol('proto.espressopb.GetConfigurationRequest', null, global);
goog.exportSymbol('proto.espressopb.TemperatureHistory', null, global);
goog.exportSymbol('proto.espressopb.TemperatureSample', null, global);
goog.exportSymbol('proto.espressopb.TemperatureStreamRequest', null, global);
goog.exportSymbol('proto.espressopb.TemperatureStreamResponse', null, global);
goog.exportSymbol('proto.espressopb.TemperatureStreamResponse.DataCase', null, global);
/**
 * Generated by JsPbCodeGenerator.
 * @param {Array=} opt_data Optional initial data array, typically from a
 * server response, or constructed directly in Javascript. The array is used
 * in place and becomes part of the constructed object. It is not cloned.
 * If no data is provided, the constructed object will be empty, but still
 * valid.
 * @extends {jspb.Message}
 * @constructor
 */
proto.espressopb.TemperatureSample = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.espressopb.TemperatureSample, jspb.Message);
if (goog.DEBUG && !COMPILED) {
  /**
   * @public
   * @override
   */
  proto.espressopb.TemperatureSample.displayName = 'proto.espressopb.TemperatureSample';
}
/**
 * Generated by JsPbCodeGenerator.
 * @param {Array=} opt_data Optional initial data array, typically from a
 * server response, or constructed directly in Javascript. The array is used
 * in place and becomes part of the constructed object. It is not cloned.
 * If no data is provided, the constructed object will be empty, but still
 * valid.
 * @extends {jspb.Message}
 * @constructor
 */
proto.espressopb.TemperatureHistory = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, proto.espressopb.TemperatureHistory.repeatedFields_, null);
};
goog.inherits(proto.espressopb.TemperatureHistory, jspb.Message);
if (goog.DEBUG && !COMPILED) {
  /**
   * @public
   * @override
   */
  proto.espressopb.TemperatureHistory.displayName = 'proto.espressopb.TemperatureHistory';
}
/**
 * Generated by JsPbCodeGenerator.
 * @param {Array=} opt_data Optional initial data array, typically from a
 * server response, or constructed directly in Javascript. The array is used
 * in place and becomes part of the constructed object. It is not cloned.
 * If no data is provided, the constructed object will be empty, but still
 * valid.
 * @extends {jspb.Message}
 * @constructor
 */
proto.espressopb.TemperatureStreamRequest = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.espressopb.TemperatureStreamRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
  /**
   * @public
   * @override
   */
  proto.espressopb.TemperatureStreamRequest.displayName = 'proto.espressopb.TemperatureStreamRequest';
}
/**
 * Generated by JsPbCodeGenerator.
 * @param {Array=} opt_data Optional initial data array, typically from a
 * server response, or constructed directly in Javascript. The array is used
 * in place and becomes part of the constructed object. It is not cloned.
 * If no data is provided, the constructed object will be empty, but still
 * valid.
 * @extends {jspb.Message}
 * @constructor
 */
proto.espressopb.TemperatureStreamResponse = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, proto.espressopb.TemperatureStreamResponse.oneofGroups_);
};
goog.inherits(proto.espressopb.TemperatureStreamResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
  /**
   * @public
   * @override
   */
  proto.espressopb.TemperatureStreamResponse.displayName = 'proto.espressopb.TemperatureStreamResponse';
}
/**
 * Generated by JsPbCodeGenerator.
 * @param {Array=} opt_data Optional initial data array, typically from a
 * server response, or constructed directly in Javascript. The array is used
 * in place and becomes part of the constructed object. It is not cloned.
 * If no data is provided, the constructed object will be empty, but still
 * valid.
 * @extends {jspb.Message}
 * @constructor
 */
proto.espressopb.GetConfigurationRequest = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.espressopb.GetConfigurationRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
  /**
   * @public
   * @override
   */
  proto.espressopb.GetConfigurationRequest.displayName = 'proto.espressopb.GetConfigurationRequest';
}
/**
 * Generated by JsPbCodeGenerator.
 * @param {Array=} opt_data Optional initial data array, typically from a
 * server response, or constructed directly in Javascript. The array is used
 * in place and becomes part of the constructed object. It is not cloned.
 * If no data is provided, the constructed object will be empty, but still
 * valid.
 * @extends {jspb.Message}
 * @constructor
 */
proto.espressopb.Configuration = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.espressopb.Configuration, jspb.Message);
if (goog.DEBUG && !COMPILED) {
  /**
   * @public
   * @override
   */
  proto.espressopb.Configuration.displayName = 'proto.espressopb.Configuration';
}



if (jspb.Message.GENERATE_TO_OBJECT) {
/**
 * Creates an object representation of this proto.
 * Field names that are reserved in JavaScript and will be renamed to pb_name.
 * Optional fields that are not set will be set to undefined.
 * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
 * For the list of reserved names please see:
 *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
 * @param {boolean=} opt_includeInstance Deprecated. whether to include the
 *     JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @return {!Object}
 */
proto.espressopb.TemperatureSample.prototype.toObject = function(opt_includeInstance) {
  return proto.espressopb.TemperatureSample.toObject(opt_includeInstance, this);
};


/**
 * Static version of the {@see toObject} method.
 * @param {boolean|undefined} includeInstance Deprecated. Whether to include
 *     the JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @param {!proto.espressopb.TemperatureSample} msg The msg instance to transform.
 * @return {!Object}
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureSample.toObject = function(includeInstance, msg) {
  var f, obj = {
    value: jspb.Message.getFloatingPointFieldWithDefault(msg, 1, 0.0),
    observedAt: (f = msg.getObservedAt()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f)
  };

  if (includeInstance) {
    obj.$jspbMessageInstance = msg;
  }
  return obj;
};
}


/**
 * Deserializes binary data (in protobuf wire format).
 * @param {jspb.ByteSource} bytes The bytes to deserialize.
 * @return {!proto.espressopb.TemperatureSample}
 */
proto.espressopb.TemperatureSample.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new proto.espressopb.TemperatureSample;
  return proto.espressopb.TemperatureSample.deserializeBinaryFromReader(msg, reader);
};


/**
 * Deserializes binary data (in protobuf wire format) from the
 * given reader into the given message object.
 * @param {!proto.espressopb.TemperatureSample} msg The message object to deserialize into.
 * @param {!jspb.BinaryReader} reader The BinaryReader to use.
 * @return {!proto.espressopb.TemperatureSample}
 */
proto.espressopb.TemperatureSample.deserializeBinaryFromReader = function(msg, reader) {
  while (reader.nextField()) {
    if (reader.isEndGroup()) {
      break;
    }
    var field = reader.getFieldNumber();
    switch (field) {
    case 1:
      var value = /** @type {number} */ (reader.readFloat());
      msg.setValue(value);
      break;
    case 2:
      var value = new google_protobuf_timestamp_pb.Timestamp;
      reader.readMessage(value,google_protobuf_timestamp_pb.Timestamp.deserializeBinaryFromReader);
      msg.setObservedAt(value);
      break;
    default:
      reader.skipField();
      break;
    }
  }
  return msg;
};


/**
 * Serializes the message to binary data (in protobuf wire format).
 * @return {!Uint8Array}
 */
proto.espressopb.TemperatureSample.prototype.serializeBinary = function() {
  var writer = new jspb.BinaryWriter();
  proto.espressopb.TemperatureSample.serializeBinaryToWriter(this, writer);
  return writer.getResultBuffer();
};


/**
 * Serializes the given message to binary data (in protobuf wire
 * format), writing to the given BinaryWriter.
 * @param {!proto.espressopb.TemperatureSample} message
 * @param {!jspb.BinaryWriter} writer
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureSample.serializeBinaryToWriter = function(message, writer) {
  var f = undefined;
  f = message.getValue();
  if (f !== 0.0) {
    writer.writeFloat(
      1,
      f
    );
  }
  f = message.getObservedAt();
  if (f != null) {
    writer.writeMessage(
      2,
      f,
      google_protobuf_timestamp_pb.Timestamp.serializeBinaryToWriter
    );
  }
};


/**
 * optional float value = 1;
 * @return {number}
 */
proto.espressopb.TemperatureSample.prototype.getValue = function() {
  return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 1, 0.0));
};


/**
 * @param {number} value
 * @return {!proto.espressopb.TemperatureSample} returns this
 */
proto.espressopb.TemperatureSample.prototype.setValue = function(value) {
  return jspb.Message.setProto3FloatField(this, 1, value);
};


/**
 * optional google.protobuf.Timestamp observed_at = 2;
 * @return {?proto.google.protobuf.Timestamp}
 */
proto.espressopb.TemperatureSample.prototype.getObservedAt = function() {
  return /** @type{?proto.google.protobuf.Timestamp} */ (
    jspb.Message.getWrapperField(this, google_protobuf_timestamp_pb.Timestamp, 2));
};


/**
 * @param {?proto.google.protobuf.Timestamp|undefined} value
 * @return {!proto.espressopb.TemperatureSample} returns this
*/
proto.espressopb.TemperatureSample.prototype.setObservedAt = function(value) {
  return jspb.Message.setWrapperField(this, 2, value);
};


/**
 * Clears the message field making it undefined.
 * @return {!proto.espressopb.TemperatureSample} returns this
 */
proto.espressopb.TemperatureSample.prototype.clearObservedAt = function() {
  return this.setObservedAt(undefined);
};


/**
 * Returns whether this field is set.
 * @return {boolean}
 */
proto.espressopb.TemperatureSample.prototype.hasObservedAt = function() {
  return jspb.Message.getField(this, 2) != null;
};



/**
 * List of repeated fields within this message type.
 * @private {!Array<number>}
 * @const
 */
proto.espressopb.TemperatureHistory.repeatedFields_ = [1];



if (jspb.Message.GENERATE_TO_OBJECT) {
/**
 * Creates an object representation of this proto.
 * Field names that are reserved in JavaScript and will be renamed to pb_name.
 * Optional fields that are not set will be set to undefined.
 * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
 * For the list of reserved names please see:
 *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
 * @param {boolean=} opt_includeInstance Deprecated. whether to include the
 *     JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @return {!Object}
 */
proto.espressopb.TemperatureHistory.prototype.toObject = function(opt_includeInstance) {
  return proto.espressopb.TemperatureHistory.toObject(opt_includeInstance, this);
};


/**
 * Static version of the {@see toObject} method.
 * @param {boolean|undefined} includeInstance Deprecated. Whether to include
 *     the JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @param {!proto.espressopb.TemperatureHistory} msg The msg instance to transform.
 * @return {!Object}
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureHistory.toObject = function(includeInstance, msg) {
  var f, obj = {
    samplesList: jspb.Message.toObjectList(msg.getSamplesList(),
    proto.espressopb.TemperatureSample.toObject, includeInstance)
  };

  if (includeInstance) {
    obj.$jspbMessageInstance = msg;
  }
  return obj;
};
}


/**
 * Deserializes binary data (in protobuf wire format).
 * @param {jspb.ByteSource} bytes The bytes to deserialize.
 * @return {!proto.espressopb.TemperatureHistory}
 */
proto.espressopb.TemperatureHistory.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new proto.espressopb.TemperatureHistory;
  return proto.espressopb.TemperatureHistory.deserializeBinaryFromReader(msg, reader);
};


/**
 * Deserializes binary data (in protobuf wire format) from the
 * given reader into the given message object.
 * @param {!proto.espressopb.TemperatureHistory} msg The message object to deserialize into.
 * @param {!jspb.BinaryReader} reader The BinaryReader to use.
 * @return {!proto.espressopb.TemperatureHistory}
 */
proto.espressopb.TemperatureHistory.deserializeBinaryFromReader = function(msg, reader) {
  while (reader.nextField()) {
    if (reader.isEndGroup()) {
      break;
    }
    var field = reader.getFieldNumber();
    switch (field) {
    case 1:
      var value = new proto.espressopb.TemperatureSample;
      reader.readMessage(value,proto.espressopb.TemperatureSample.deserializeBinaryFromReader);
      msg.addSamples(value);
      break;
    default:
      reader.skipField();
      break;
    }
  }
  return msg;
};


/**
 * Serializes the message to binary data (in protobuf wire format).
 * @return {!Uint8Array}
 */
proto.espressopb.TemperatureHistory.prototype.serializeBinary = function() {
  var writer = new jspb.BinaryWriter();
  proto.espressopb.TemperatureHistory.serializeBinaryToWriter(this, writer);
  return writer.getResultBuffer();
};


/**
 * Serializes the given message to binary data (in protobuf wire
 * format), writing to the given BinaryWriter.
 * @param {!proto.espressopb.TemperatureHistory} message
 * @param {!jspb.BinaryWriter} writer
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureHistory.serializeBinaryToWriter = function(message, writer) {
  var f = undefined;
  f = message.getSamplesList();
  if (f.length > 0) {
    writer.writeRepeatedMessage(
      1,
      f,
      proto.espressopb.TemperatureSample.serializeBinaryToWriter
    );
  }
};


/**
 * repeated TemperatureSample samples = 1;
 * @return {!Array<!proto.espressopb.TemperatureSample>}
 */
proto.espressopb.TemperatureHistory.prototype.getSamplesList = function() {
  return /** @type{!Array<!proto.espressopb.TemperatureSample>} */ (
    jspb.Message.getRepeatedWrapperField(this, proto.espressopb.TemperatureSample, 1));
};


/**
 * @param {!Array<!proto.espressopb.TemperatureSample>} value
 * @return {!proto.espressopb.TemperatureHistory} returns this
*/
proto.espressopb.TemperatureHistory.prototype.setSamplesList = function(value) {
  return jspb.Message.setRepeatedWrapperField(this, 1, value);
};


/**
 * @param {!proto.espressopb.TemperatureSample=} opt_value
 * @param {number=} opt_index
 * @return {!proto.espressopb.TemperatureSample}
 */
proto.espressopb.TemperatureHistory.prototype.addSamples = function(opt_value, opt_index) {
  return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.espressopb.TemperatureSample, opt_index);
};


/**
 * Clears the list making it empty but non-null.
 * @return {!proto.espressopb.TemperatureHistory} returns this
 */
proto.espressopb.TemperatureHistory.prototype.clearSamplesList = function() {
  return this.setSamplesList([]);
};





if (jspb.Message.GENERATE_TO_OBJECT) {
/**
 * Creates an object representation of this proto.
 * Field names that are reserved in JavaScript and will be renamed to pb_name.
 * Optional fields that are not set will be set to undefined.
 * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
 * For the list of reserved names please see:
 *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
 * @param {boolean=} opt_includeInstance Deprecated. whether to include the
 *     JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @return {!Object}
 */
proto.espressopb.TemperatureStreamRequest.prototype.toObject = function(opt_includeInstance) {
  return proto.espressopb.TemperatureStreamRequest.toObject(opt_includeInstance, this);
};


/**
 * Static version of the {@see toObject} method.
 * @param {boolean|undefined} includeInstance Deprecated. Whether to include
 *     the JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @param {!proto.espressopb.TemperatureStreamRequest} msg The msg instance to transform.
 * @return {!Object}
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureStreamRequest.toObject = function(includeInstance, msg) {
  var f, obj = {

  };

  if (includeInstance) {
    obj.$jspbMessageInstance = msg;
  }
  return obj;
};
}


/**
 * Deserializes binary data (in protobuf wire format).
 * @param {jspb.ByteSource} bytes The bytes to deserialize.
 * @return {!proto.espressopb.TemperatureStreamRequest}
 */
proto.espressopb.TemperatureStreamRequest.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new proto.espressopb.TemperatureStreamRequest;
  return proto.espressopb.TemperatureStreamRequest.deserializeBinaryFromReader(msg, reader);
};


/**
 * Deserializes binary data (in protobuf wire format) from the
 * given reader into the given message object.
 * @param {!proto.espressopb.TemperatureStreamRequest} msg The message object to deserialize into.
 * @param {!jspb.BinaryReader} reader The BinaryReader to use.
 * @return {!proto.espressopb.TemperatureStreamRequest}
 */
proto.espressopb.TemperatureStreamRequest.deserializeBinaryFromReader = function(msg, reader) {
  while (reader.nextField()) {
    if (reader.isEndGroup()) {
      break;
    }
    var field = reader.getFieldNumber();
    switch (field) {
    default:
      reader.skipField();
      break;
    }
  }
  return msg;
};


/**
 * Serializes the message to binary data (in protobuf wire format).
 * @return {!Uint8Array}
 */
proto.espressopb.TemperatureStreamRequest.prototype.serializeBinary = function() {
  var writer = new jspb.BinaryWriter();
  proto.espressopb.TemperatureStreamRequest.serializeBinaryToWriter(this, writer);
  return writer.getResultBuffer();
};


/**
 * Serializes the given message to binary data (in protobuf wire
 * format), writing to the given BinaryWriter.
 * @param {!proto.espressopb.TemperatureStreamRequest} message
 * @param {!jspb.BinaryWriter} writer
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureStreamRequest.serializeBinaryToWriter = function(message, writer) {
  var f = undefined;
};



/**
 * Oneof group definitions for this message. Each group defines the field
 * numbers belonging to that group. When of these fields' value is set, all
 * other fields in the group are cleared. During deserialization, if multiple
 * fields are encountered for a group, only the last value seen will be kept.
 * @private {!Array<!Array<number>>}
 * @const
 */
proto.espressopb.TemperatureStreamResponse.oneofGroups_ = [[1,2]];

/**
 * @enum {number}
 */
proto.espressopb.TemperatureStreamResponse.DataCase = {
  DATA_NOT_SET: 0,
  HISTORY: 1,
  SAMPLE: 2
};

/**
 * @return {proto.espressopb.TemperatureStreamResponse.DataCase}
 */
proto.espressopb.TemperatureStreamResponse.prototype.getDataCase = function() {
  return /** @type {proto.espressopb.TemperatureStreamResponse.DataCase} */(jspb.Message.computeOneofCase(this, proto.espressopb.TemperatureStreamResponse.oneofGroups_[0]));
};



if (jspb.Message.GENERATE_TO_OBJECT) {
/**
 * Creates an object representation of this proto.
 * Field names that are reserved in JavaScript and will be renamed to pb_name.
 * Optional fields that are not set will be set to undefined.
 * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
 * For the list of reserved names please see:
 *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
 * @param {boolean=} opt_includeInstance Deprecated. whether to include the
 *     JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @return {!Object}
 */
proto.espressopb.TemperatureStreamResponse.prototype.toObject = function(opt_includeInstance) {
  return proto.espressopb.TemperatureStreamResponse.toObject(opt_includeInstance, this);
};


/**
 * Static version of the {@see toObject} method.
 * @param {boolean|undefined} includeInstance Deprecated. Whether to include
 *     the JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @param {!proto.espressopb.TemperatureStreamResponse} msg The msg instance to transform.
 * @return {!Object}
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureStreamResponse.toObject = function(includeInstance, msg) {
  var f, obj = {
    history: (f = msg.getHistory()) && proto.espressopb.TemperatureHistory.toObject(includeInstance, f),
    sample: (f = msg.getSample()) && proto.espressopb.TemperatureSample.toObject(includeInstance, f)
  };

  if (includeInstance) {
    obj.$jspbMessageInstance = msg;
  }
  return obj;
};
}


/**
 * Deserializes binary data (in protobuf wire format).
 * @param {jspb.ByteSource} bytes The bytes to deserialize.
 * @return {!proto.espressopb.TemperatureStreamResponse}
 */
proto.espressopb.TemperatureStreamResponse.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new proto.espressopb.TemperatureStreamResponse;
  return proto.espressopb.TemperatureStreamResponse.deserializeBinaryFromReader(msg, reader);
};


/**
 * Deserializes binary data (in protobuf wire format) from the
 * given reader into the given message object.
 * @param {!proto.espressopb.TemperatureStreamResponse} msg The message object to deserialize into.
 * @param {!jspb.BinaryReader} reader The BinaryReader to use.
 * @return {!proto.espressopb.TemperatureStreamResponse}
 */
proto.espressopb.TemperatureStreamResponse.deserializeBinaryFromReader = function(msg, reader) {
  while (reader.nextField()) {
    if (reader.isEndGroup()) {
      break;
    }
    var field = reader.getFieldNumber();
    switch (field) {
    case 1:
      var value = new proto.espressopb.TemperatureHistory;
      reader.readMessage(value,proto.espressopb.TemperatureHistory.deserializeBinaryFromReader);
      msg.setHistory(value);
      break;
    case 2:
      var value = new proto.espressopb.TemperatureSample;
      reader.readMessage(value,proto.espressopb.TemperatureSample.deserializeBinaryFromReader);
      msg.setSample(value);
      break;
    default:
      reader.skipField();
      break;
    }
  }
  return msg;
};


/**
 * Serializes the message to binary data (in protobuf wire format).
 * @return {!Uint8Array}
 */
proto.espressopb.TemperatureStreamResponse.prototype.serializeBinary = function() {
  var writer = new jspb.BinaryWriter();
  proto.espressopb.TemperatureStreamResponse.serializeBinaryToWriter(this, writer);
  return writer.getResultBuffer();
};


/**
 * Serializes the given message to binary data (in protobuf wire
 * format), writing to the given BinaryWriter.
 * @param {!proto.espressopb.TemperatureStreamResponse} message
 * @param {!jspb.BinaryWriter} writer
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.TemperatureStreamResponse.serializeBinaryToWriter = function(message, writer) {
  var f = undefined;
  f = message.getHistory();
  if (f != null) {
    writer.writeMessage(
      1,
      f,
      proto.espressopb.TemperatureHistory.serializeBinaryToWriter
    );
  }
  f = message.getSample();
  if (f != null) {
    writer.writeMessage(
      2,
      f,
      proto.espressopb.TemperatureSample.serializeBinaryToWriter
    );
  }
};


/**
 * optional TemperatureHistory history = 1;
 * @return {?proto.espressopb.TemperatureHistory}
 */
proto.espressopb.TemperatureStreamResponse.prototype.getHistory = function() {
  return /** @type{?proto.espressopb.TemperatureHistory} */ (
    jspb.Message.getWrapperField(this, proto.espressopb.TemperatureHistory, 1));
};


/**
 * @param {?proto.espressopb.TemperatureHistory|undefined} value
 * @return {!proto.espressopb.TemperatureStreamResponse} returns this
*/
proto.espressopb.TemperatureStreamResponse.prototype.setHistory = function(value) {
  return jspb.Message.setOneofWrapperField(this, 1, proto.espressopb.TemperatureStreamResponse.oneofGroups_[0], value);
};


/**
 * Clears the message field making it undefined.
 * @return {!proto.espressopb.TemperatureStreamResponse} returns this
 */
proto.espressopb.TemperatureStreamResponse.prototype.clearHistory = function() {
  return this.setHistory(undefined);
};


/**
 * Returns whether this field is set.
 * @return {boolean}
 */
proto.espressopb.TemperatureStreamResponse.prototype.hasHistory = function() {
  return jspb.Message.getField(this, 1) != null;
};


/**
 * optional TemperatureSample sample = 2;
 * @return {?proto.espressopb.TemperatureSample}
 */
proto.espressopb.TemperatureStreamResponse.prototype.getSample = function() {
  return /** @type{?proto.espressopb.TemperatureSample} */ (
    jspb.Message.getWrapperField(this, proto.espressopb.TemperatureSample, 2));
};


/**
 * @param {?proto.espressopb.TemperatureSample|undefined} value
 * @return {!proto.espressopb.TemperatureStreamResponse} returns this
*/
proto.espressopb.TemperatureStreamResponse.prototype.setSample = function(value) {
  return jspb.Message.setOneofWrapperField(this, 2, proto.espressopb.TemperatureStreamResponse.oneofGroups_[0], value);
};


/**
 * Clears the message field making it undefined.
 * @return {!proto.espressopb.TemperatureStreamResponse} returns this
 */
proto.espressopb.TemperatureStreamResponse.prototype.clearSample = function() {
  return this.setSample(undefined);
};


/**
 * Returns whether this field is set.
 * @return {boolean}
 */
proto.espressopb.TemperatureStreamResponse.prototype.hasSample = function() {
  return jspb.Message.getField(this, 2) != null;
};





if (jspb.Message.GENERATE_TO_OBJECT) {
/**
 * Creates an object representation of this proto.
 * Field names that are reserved in JavaScript and will be renamed to pb_name.
 * Optional fields that are not set will be set to undefined.
 * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
 * For the list of reserved names please see:
 *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
 * @param {boolean=} opt_includeInstance Deprecated. whether to include the
 *     JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @return {!Object}
 */
proto.espressopb.GetConfigurationRequest.prototype.toObject = function(opt_includeInstance) {
  return proto.espressopb.GetConfigurationRequest.toObject(opt_includeInstance, this);
};


/**
 * Static version of the {@see toObject} method.
 * @param {boolean|undefined} includeInstance Deprecated. Whether to include
 *     the JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @param {!proto.espressopb.GetConfigurationRequest} msg The msg instance to transform.
 * @return {!Object}
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.GetConfigurationRequest.toObject = function(includeInstance, msg) {
  var f, obj = {

  };

  if (includeInstance) {
    obj.$jspbMessageInstance = msg;
  }
  return obj;
};
}


/**
 * Deserializes binary data (in protobuf wire format).
 * @param {jspb.ByteSource} bytes The bytes to deserialize.
 * @return {!proto.espressopb.GetConfigurationRequest}
 */
proto.espressopb.GetConfigurationRequest.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new proto.espressopb.GetConfigurationRequest;
  return proto.espressopb.GetConfigurationRequest.deserializeBinaryFromReader(msg, reader);
};


/**
 * Deserializes binary data (in protobuf wire format) from the
 * given reader into the given message object.
 * @param {!proto.espressopb.GetConfigurationRequest} msg The message object to deserialize into.
 * @param {!jspb.BinaryReader} reader The BinaryReader to use.
 * @return {!proto.espressopb.GetConfigurationRequest}
 */
proto.espressopb.GetConfigurationRequest.deserializeBinaryFromReader = function(msg, reader) {
  while (reader.nextField()) {
    if (reader.isEndGroup()) {
      break;
    }
    var field = reader.getFieldNumber();
    switch (field) {
    default:
      reader.skipField();
      break;
    }
  }
  return msg;
};


/**
 * Serializes the message to binary data (in protobuf wire format).
 * @return {!Uint8Array}
 */
proto.espressopb.GetConfigurationRequest.prototype.serializeBinary = function() {
  var writer = new jspb.BinaryWriter();
  proto.espressopb.GetConfigurationRequest.serializeBinaryToWriter(this, writer);
  return writer.getResultBuffer();
};


/**
 * Serializes the given message to binary data (in protobuf wire
 * format), writing to the given BinaryWriter.
 * @param {!proto.espressopb.GetConfigurationRequest} message
 * @param {!jspb.BinaryWriter} writer
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.GetConfigurationRequest.serializeBinaryToWriter = function(message, writer) {
  var f = undefined;
};





if (jspb.Message.GENERATE_TO_OBJECT) {
/**
 * Creates an object representation of this proto.
 * Field names that are reserved in JavaScript and will be renamed to pb_name.
 * Optional fields that are not set will be set to undefined.
 * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
 * For the list of reserved names please see:
 *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
 * @param {boolean=} opt_includeInstance Deprecated. whether to include the
 *     JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @return {!Object}
 */
proto.espressopb.Configuration.prototype.toObject = function(opt_includeInstance) {
  return proto.espressopb.Configuration.toObject(opt_includeInstance, this);
};


/**
 * Static version of the {@see toObject} method.
 * @param {boolean|undefined} includeInstance Deprecated. Whether to include
 *     the JSPB instance for transitional soy proto support:
 *     http://goto/soy-param-migration
 * @param {!proto.espressopb.Configuration} msg The msg instance to transform.
 * @return {!Object}
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.Configuration.toObject = function(includeInstance, msg) {
  var f, obj = {
    temperature: jspb.Message.getFloatingPointFieldWithDefault(msg, 1, 0.0),
    p: jspb.Message.getFloatingPointFieldWithDefault(msg, 2, 0.0),
    i: jspb.Message.getFloatingPointFieldWithDefault(msg, 3, 0.0),
    d: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0),
    setAt: (f = msg.getSetAt()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f)
  };

  if (includeInstance) {
    obj.$jspbMessageInstance = msg;
  }
  return obj;
};
}


/**
 * Deserializes binary data (in protobuf wire format).
 * @param {jspb.ByteSource} bytes The bytes to deserialize.
 * @return {!proto.espressopb.Configuration}
 */
proto.espressopb.Configuration.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new proto.espressopb.Configuration;
  return proto.espressopb.Configuration.deserializeBinaryFromReader(msg, reader);
};


/**
 * Deserializes binary data (in protobuf wire format) from the
 * given reader into the given message object.
 * @param {!proto.espressopb.Configuration} msg The message object to deserialize into.
 * @param {!jspb.BinaryReader} reader The BinaryReader to use.
 * @return {!proto.espressopb.Configuration}
 */
proto.espressopb.Configuration.deserializeBinaryFromReader = function(msg, reader) {
  while (reader.nextField()) {
    if (reader.isEndGroup()) {
      break;
    }
    var field = reader.getFieldNumber();
    switch (field) {
    case 1:
      var value = /** @type {number} */ (reader.readFloat());
      msg.setTemperature(value);
      break;
    case 2:
      var value = /** @type {number} */ (reader.readFloat());
      msg.setP(value);
      break;
    case 3:
      var value = /** @type {number} */ (reader.readFloat());
      msg.setI(value);
      break;
    case 4:
      var value = /** @type {number} */ (reader.readFloat());
      msg.setD(value);
      break;
    case 5:
      var value = new google_protobuf_timestamp_pb.Timestamp;
      reader.readMessage(value,google_protobuf_timestamp_pb.Timestamp.deserializeBinaryFromReader);
      msg.setSetAt(value);
      break;
    default:
      reader.skipField();
      break;
    }
  }
  return msg;
};


/**
 * Serializes the message to binary data (in protobuf wire format).
 * @return {!Uint8Array}
 */
proto.espressopb.Configuration.prototype.serializeBinary = function() {
  var writer = new jspb.BinaryWriter();
  proto.espressopb.Configuration.serializeBinaryToWriter(this, writer);
  return writer.getResultBuffer();
};


/**
 * Serializes the given message to binary data (in protobuf wire
 * format), writing to the given BinaryWriter.
 * @param {!proto.espressopb.Configuration} message
 * @param {!jspb.BinaryWriter} writer
 * @suppress {unusedLocalVariables} f is only used for nested messages
 */
proto.espressopb.Configuration.serializeBinaryToWriter = function(message, writer) {
  var f = undefined;
  f = message.getTemperature();
  if (f !== 0.0) {
    writer.writeFloat(
      1,
      f
    );
  }
  f = message.getP();
  if (f !== 0.0) {
    writer.writeFloat(
      2,
      f
    );
  }
  f = message.getI();
  if (f !== 0.0) {
    writer.writeFloat(
      3,
      f
    );
  }
  f = message.getD();
  if (f !== 0.0) {
    writer.writeFloat(
      4,
      f
    );
  }
  f = message.getSetAt();
  if (f != null) {
    writer.writeMessage(
      5,
      f,
      google_protobuf_timestamp_pb.Timestamp.serializeBinaryToWriter
    );
  }
};


/**
 * optional float temperature = 1;
 * @return {number}
 */
proto.espressopb.Configuration.prototype.getTemperature = function() {
  return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 1, 0.0));
};


/**
 * @param {number} value
 * @return {!proto.espressopb.Configuration} returns this
 */
proto.espressopb.Configuration.prototype.setTemperature = function(value) {
  return jspb.Message.setProto3FloatField(this, 1, value);
};


/**
 * optional float p = 2;
 * @return {number}
 */
proto.espressopb.Configuration.prototype.getP = function() {
  return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 2, 0.0));
};


/**
 * @param {number} value
 * @return {!proto.espressopb.Configuration} returns this
 */
proto.espressopb.Configuration.prototype.setP = function(value) {
  return jspb.Message.setProto3FloatField(this, 2, value);
};


/**
 * optional float i = 3;
 * @return {number}
 */
proto.espressopb.Configuration.prototype.getI = function() {
  return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 3, 0.0));
};


/**
 * @param {number} value
 * @return {!proto.espressopb.Configuration} returns this
 */
proto.espressopb.Configuration.prototype.setI = function(value) {
  return jspb.Message.setProto3FloatField(this, 3, value);
};


/**
 * optional float d = 4;
 * @return {number}
 */
proto.espressopb.Configuration.prototype.getD = function() {
  return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 4, 0.0));
};


/**
 * @param {number} value
 * @return {!proto.espressopb.Configuration} returns this
 */
proto.espressopb.Configuration.prototype.setD = function(value) {
  return jspb.Message.setProto3FloatField(this, 4, value);
};


/**
 * optional google.protobuf.Timestamp set_at = 5;
 * @return {?proto.google.protobuf.Timestamp}
 */
proto.espressopb.Configuration.prototype.getSetAt = function() {
  return /** @type{?proto.google.protobuf.Timestamp} */ (
    jspb.Message.getWrapperField(this, google_protobuf_timestamp_pb.Timestamp, 5));
};


/**
 * @param {?proto.google.protobuf.Timestamp|undefined} value
 * @return {!proto.espressopb.Configuration} returns this
*/
proto.espressopb.Configuration.prototype.setSetAt = function(value) {
  return jspb.Message.setWrapperField(this, 5, value);
};


/**
 * Clears the message field making it undefined.
 * @return {!proto.espressopb.Configuration} returns this
 */
proto.espressopb.Configuration.prototype.clearSetAt = function() {
  return this.setSetAt(undefined);
};


/**
 * Returns whether this field is set.
 * @return {boolean}
 */
proto.espressopb.Configuration.prototype.hasSetAt = function() {
  return jspb.Message.getField(this, 5) != null;
};


goog.object.extend(exports, proto.espressopb);


<|start_filename|>pkg/control/control.go<|end_filename|>
package control

import (
	"time"
)

type TargetTemperature struct {
	Value float32
	SetAt time.Time
}


<|start_filename|>pkg/espressopb/espresso.pb.go<|end_filename|>
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: espresso.proto

package espressopb

import (
	context "context"
	fmt "fmt"
	proto "github.com/golang/protobuf/proto"
	timestamp "github.com/golang/protobuf/ptypes/timestamp"
	grpc "google.golang.org/grpc"
	codes "google.golang.org/grpc/codes"
	status "google.golang.org/grpc/status"
	math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

type TemperatureSample struct {
	Value                float32              `protobuf:"fixed32,1,opt,name=value,proto3" json:"value,omitempty"`
	ObservedAt           *timestamp.Timestamp `protobuf:"bytes,2,opt,name=observed_at,json=observedAt,proto3" json:"observed_at,omitempty"`
	XXX_NoUnkeyedLiteral struct{}             `json:"-"`
	XXX_unrecognized     []byte               `json:"-"`
	XXX_sizecache        int32                `json:"-"`
}

func (m *TemperatureSample) Reset()         { *m = TemperatureSample{} }
func (m *TemperatureSample) String() string { return proto.CompactTextString(m) }
func (*TemperatureSample) ProtoMessage()    {}
func (*TemperatureSample) Descriptor() ([]byte, []int) {
	return fileDescriptor_445399412d1702d2, []int{0}
}

func (m *TemperatureSample) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_TemperatureSample.Unmarshal(m, b)
}
func (m *TemperatureSample) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_TemperatureSample.Marshal(b, m, deterministic)
}
func (m *TemperatureSample) XXX_Merge(src proto.Message) {
	xxx_messageInfo_TemperatureSample.Merge(m, src)
}
func (m *TemperatureSample) XXX_Size() int {
	return xxx_messageInfo_TemperatureSample.Size(m)
}
func (m *TemperatureSample) XXX_DiscardUnknown() {
	xxx_messageInfo_TemperatureSample.DiscardUnknown(m)
}

var xxx_messageInfo_TemperatureSample proto.InternalMessageInfo

func (m *TemperatureSample) GetValue() float32 {
	if m != nil {
		return m.Value
	}
	return 0
}

func (m *TemperatureSample) GetObservedAt() *timestamp.Timestamp {
	if m != nil {
		return m.ObservedAt
	}
	return nil
}

type TemperatureHistory struct {
	Samples              []*TemperatureSample `protobuf:"bytes,1,rep,name=samples,proto3" json:"samples,omitempty"`
	XXX_NoUnkeyedLiteral struct{}             `json:"-"`
	XXX_unrecognized     []byte               `json:"-"`
	XXX_sizecache        int32                `json:"-"`
}

func (m *TemperatureHistory) Reset()         { *m = TemperatureHistory{} }
func (m *TemperatureHistory) String() string { return proto.CompactTextString(m) }
func (*TemperatureHistory) ProtoMessage()    {}
func (*TemperatureHistory) Descriptor() ([]byte, []int) {
	return fileDescriptor_445399412d1702d2, []int{1}
}

func (m *TemperatureHistory) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_TemperatureHistory.Unmarshal(m, b)
}
func (m *TemperatureHistory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_TemperatureHistory.Marshal(b, m, deterministic)
}
func (m *TemperatureHistory) XXX_Merge(src proto.Message) {
	xxx_messageInfo_TemperatureHistory.Merge(m, src)
}
func (m *TemperatureHistory) XXX_Size() int {
	return xxx_messageInfo_TemperatureHistory.Size(m)
}
func (m *TemperatureHistory) XXX_DiscardUnknown() {
	xxx_messageInfo_TemperatureHistory.DiscardUnknown(m)
}

var xxx_messageInfo_TemperatureHistory proto.InternalMessageInfo

func (m *TemperatureHistory) GetSamples() []*TemperatureSample {
	if m != nil {
		return m.Samples
	}
	return nil
}

type TemperatureStreamRequest struct {
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (m *TemperatureStreamRequest) Reset()         { *m = TemperatureStreamRequest{} }
func (m *TemperatureStreamRequest) String() string { return proto.CompactTextString(m) }
func (*TemperatureStreamRequest) ProtoMessage()    {}
func (*TemperatureStreamRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_445399412d1702d2, []int{2}
}

func (m *TemperatureStreamRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_TemperatureStreamRequest.Unmarshal(m, b)
}
func (m *TemperatureStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_TemperatureStreamRequest.Marshal(b, m, deterministic)
}
func (m *TemperatureStreamRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_TemperatureStreamRequest.Merge(m, src)
}
func (m *TemperatureStreamRequest) XXX_Size() int {
	return xxx_messageInfo_TemperatureStreamRequest.Size(m)
}
func (m *TemperatureStreamRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_TemperatureStreamRequest.DiscardUnknown(m)
}

var xxx_messageInfo_TemperatureStreamRequest proto.InternalMessageInfo

type TemperatureStreamResponse struct {
	// Types that are valid to be assigned to Data:
	//	*TemperatureStreamResponse_History
	//	*TemperatureStreamResponse_Sample
	Data                 isTemperatureStreamResponse_Data `protobuf_oneof:"data"`
	XXX_NoUnkeyedLiteral struct{}                         `json:"-"`
	XXX_unrecognized     []byte                           `json:"-"`
	XXX_sizecache        int32                            `json:"-"`
}

func (m *TemperatureStreamResponse) Reset()         { *m = TemperatureStreamResponse{} }
func (m *TemperatureStreamResponse) String() string { return proto.CompactTextString(m) }
func (*TemperatureStreamResponse) ProtoMessage()    {}
func (*TemperatureStreamResponse) Descriptor() ([]byte, []int) {
	return fileDescriptor_445399412d1702d2, []int{3}
}

func (m *TemperatureStreamResponse) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_TemperatureStreamResponse.Unmarshal(m, b)
}
func (m *TemperatureStreamResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_TemperatureStreamResponse.Marshal(b, m, deterministic)
}
func (m *TemperatureStreamResponse) XXX_Merge(src proto.Message) {
	xxx_messageInfo_TemperatureStreamResponse.Merge(m, src)
}
func (m *TemperatureStreamResponse) XXX_Size() int {
	return xxx_messageInfo_TemperatureStreamResponse.Size(m)
}
func (m *TemperatureStreamResponse) XXX_DiscardUnknown() {
	xxx_messageInfo_TemperatureStreamResponse.DiscardUnknown(m)
}

var xxx_messageInfo_TemperatureStreamResponse proto.InternalMessageInfo

type isTemperatureStreamResponse_Data interface {
	isTemperatureStreamResponse_Data()
}

type TemperatureStreamResponse_History struct {
	History *TemperatureHistory `protobuf:"bytes,1,opt,name=history,proto3,oneof"`
}

type TemperatureStreamResponse_Sample struct {
	Sample *TemperatureSample `protobuf:"bytes,2,opt,name=sample,proto3,oneof"`
}

func (*TemperatureStreamResponse_History) isTemperatureStreamResponse_Data() {}

func (*TemperatureStreamResponse_Sample) isTemperatureStreamResponse_Data() {}

func (m *TemperatureStreamResponse) GetData() isTemperatureStreamResponse_Data {
	if m != nil {
		return m.Data
	}
	return nil
}

func (m *TemperatureStreamResponse) GetHistory() *TemperatureHistory {
	if x, ok := m.GetData().(*TemperatureStreamResponse_History); ok {
		return x.History
	}
	return nil
}

func (m *TemperatureStreamResponse) GetSample() *TemperatureSample {
	if x, ok := m.GetData().(*TemperatureStreamResponse_Sample); ok {
		return x.Sample
	}
	return nil
}

// XXX_OneofWrappers is for the internal use of the proto package.
func (*TemperatureStreamResponse) XXX_OneofWrappers() []interface{} {
	return []interface{}{
		(*TemperatureStreamResponse_History)(nil),
		(*TemperatureStreamResponse_Sample)(nil),
	}
}

type GetConfigurationRequest struct {
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (m *GetConfigurationRequest) Reset()         { *m = GetConfigurationRequest{} }
func (m *GetConfigurationRequest) String() string { return proto.CompactTextString(m) }
func (*GetConfigurationRequest) ProtoMessage()    {}
func (*GetConfigurationRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_445399412d1702d2, []int{4}
}

func (m *GetConfigurationRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_GetConfigurationRequest.Unmarshal(m, b)
}
func (m *GetConfigurationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_GetConfigurationRequest.Marshal(b, m, deterministic)
}
func (m *GetConfigurationRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_GetConfigurationRequest.Merge(m, src)
}
func (m *GetConfigurationRequest) XXX_Size() int {
	return xxx_messageInfo_GetConfigurationRequest.Size(m)
}
func (m *GetConfigurationRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_GetConfigurationRequest.DiscardUnknown(m)
}

var xxx_messageInfo_GetConfigurationRequest proto.InternalMessageInfo

type Configuration struct {
	Temperature          float32              `protobuf:"fixed32,1,opt,name=temperature,proto3" json:"temperature,omitempty"`
	P                    float32              `protobuf:"fixed32,2,opt,name=p,proto3" json:"p,omitempty"`
	I                    float32              `protobuf:"fixed32,3,opt,name=i,proto3" json:"i,omitempty"`
	D                    float32              `protobuf:"fixed32,4,opt,name=d,proto3" json:"d,omitempty"`
	SetAt                *timestamp.Timestamp `protobuf:"bytes,5,opt,name=set_at,json=setAt,proto3" json:"set_at,omitempty"`
	XXX_NoUnkeyedLiteral struct{}             `json:"-"`
	XXX_unrecognized     []byte               `json:"-"`
	XXX_sizecache        int32                `json:"-"`
}

func (m *Configuration) Reset()         { *m = Configuration{} }
func (m *Configuration) String() string { return proto.CompactTextString(m) }
func (*Configuration) ProtoMessage()    {}
func (*Configuration) Descriptor() ([]byte, []int) {
	return fileDescriptor_445399412d1702d2, []int{5}
}

func (m *Configuration) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_Configuration.Unmarshal(m, b)
}
func (m *Configuration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_Configuration.Marshal(b, m, deterministic)
}
func (m *Configuration) XXX_Merge(src proto.Message) {
	xxx_messageInfo_Configuration.Merge(m, src)
}
func (m *Configuration) XXX_Size() int {
	return xxx_messageInfo_Configuration.Size(m)
}
func (m *Configuration) XXX_DiscardUnknown() {
	xxx_messageInfo_Configuration.DiscardUnknown(m)
}

var xxx_messageInfo_Configuration proto.InternalMessageInfo

func (m *Configuration) GetTemperature() float32 {
	if m != nil {
		return m.Temperature
	}
	return 0
}

func (m *Configuration) GetP() float32 {
	if m != nil {
		return m.P
	}
	return 0
}

func (m *Configuration) GetI() float32 {
	if m != nil {
		return m.I
	}
	return 0
}

func (m *Configuration) GetD() float32 {
	if m != nil {
		return m.D
	}
	return 0
}

func (m *Configuration) GetSetAt() *timestamp.Timestamp {
	if m != nil {
		return m.SetAt
	}
	return nil
}

func init() {
	proto.RegisterType((*TemperatureSample)(nil), "espressopb.TemperatureSample")
	proto.RegisterType((*TemperatureHistory)(nil), "espressopb.TemperatureHistory")
	proto.RegisterType((*TemperatureStreamRequest)(nil), "espressopb.TemperatureStreamRequest")
	proto.RegisterType((*TemperatureStreamResponse)(nil), "espressopb.TemperatureStreamResponse")
	proto.RegisterType((*GetConfigurationRequest)(nil), "espressopb.GetConfigurationRequest")
	proto.RegisterType((*Configuration)(nil), "espressopb.Configuration")
}

func init() {
	proto.RegisterFile("espresso.proto", fileDescriptor_445399412d1702d2)
}

var fileDescriptor_445399412d1702d2 = []byte{
	// 394 bytes of a gzipped FileDescriptorProto
	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x51, 0xc1, 0x6e, 0xd3, 0x40,
	0x10, 0xed, 0xba, 0x8d, 0x8b, 0xc6, 0x80, 0xda, 0x15, 0x12, 0x8e, 0x25, 0xc0, 0x32, 0x20, 0xe5,
	0xe4, 0x82, 0x39, 0x54, 0x82, 0x53, 0x8b, 0x10, 0xbe, 0x70, 0xd9, 0xf6, 0x8e, 0xd6, 0xf2, 0x24,
	0x58, 0xb2, 0xb3, 0xcb, 0xee, 0xb8, 0x12, 0xff, 0x80, 0x38, 0xf3, 0xb9, 0x28, 0x5e, 0x5b, 0x75,
	0x42, 0x9d, 0x1c, 0xdf, 0xce, 0xdb, 0x79, 0xef, 0xcd, 0x83, 0xa7, 0x68, 0xb5, 0x41, 0x6b, 0x55,
	0xaa, 0x8d, 0x22, 0xc5, 0x61, 0xc0, 0xba, 0x88, 0x5e, 0xad, 0x94, 0x5a, 0xd5, 0x78, 0xd1, 0x4d,
	0x8a, 0x76, 0x79, 0x41, 0x55, 0x83, 0x96, 0x64, 0xa3, 0x1d, 0x39, 0x59, 0xc2, 0xf9, 0x2d, 0x36,
	0x1a, 0x8d, 0xa4, 0xd6, 0xe0, 0x8d, 0x6c, 0x74, 0x8d, 0xfc, 0x19, 0xcc, 0xee, 0x64, 0xdd, 0x62,
	0xc8, 0x62, 0xb6, 0xf0, 0x84, 0x03, 0xfc, 0x13, 0x04, 0xaa, 0xb0, 0x68, 0xee, 0xb0, 0xfc, 0x2e,
	0x29, 0xf4, 0x62, 0xb6, 0x08, 0xb2, 0x28, 0x75, 0x0a, 0xe9, 0xa0, 0x90, 0xde, 0x0e, 0x0a, 0x02,
	0x06, 0xfa, 0x15, 0x25, 0xdf, 0x80, 0x8f, 0x74, 0xf2, 0xca, 0x92, 0x32, 0xbf, 0xf8, 0x25, 0x9c,
	0xda, 0x4e, 0xd2, 0x86, 0x2c, 0x3e, 0x5e, 0x04, 0xd9, 0x8b, 0xf4, 0xde, 0x7c, 0xfa, 0x9f, 0x31,
	0x31, 0xb0, 0x93, 0x08, 0xc2, 0xf1, 0x94, 0x0c, 0xca, 0x46, 0xe0, 0xcf, 0x16, 0x2d, 0x25, 0x7f,
	0x19, 0xcc, 0x1f, 0x18, 0x5a, 0xad, 0xd6, 0x16, 0xf9, 0x47, 0x38, 0xfd, 0xe1, 0xd4, 0xbb, 0x74,
	0x41, 0xf6, 0x72, 0x42, 0xb2, 0xf7, 0x98, 0x1f, 0x89, 0xe1, 0x03, 0xbf, 0x04, 0xdf, 0x19, 0xe8,
	0xc3, 0xef, 0x77, 0x9b, 0x1f, 0x89, 0x9e, 0x7e, 0xed, 0xc3, 0x49, 0x29, 0x49, 0x26, 0x73, 0x78,
	0xfe, 0x15, 0xe9, 0xb3, 0x5a, 0x2f, 0xab, 0x55, 0x6b, 0x24, 0x55, 0x6a, 0x3d, 0xb8, 0xfe, 0xc3,
	0xe0, 0xc9, 0xd6, 0x80, 0xc7, 0x10, 0xd0, 0xfd, 0xce, 0xbe, 0x8b, 0xf1, 0x13, 0x7f, 0x0c, 0x4c,
	0x77, 0x56, 0x3c, 0xc1, 0xf4, 0x06, 0x55, 0xe1, 0xb1, 0x43, 0xd5, 0x06, 0x95, 0xe1, 0x89, 0x43,
	0x25, 0x7f, 0x0f, 0xbe, 0x45, 0xda, 0xd4, 0x36, 0x3b, 0x58, 0xdb, 0xcc, 0x22, 0x5d, 0x51, 0xf6,
	0xdb, 0x83, 0x47, 0x5f, 0xfa, 0x78, 0xbc, 0x80, 0xf3, 0x6b, 0x55, 0xd5, 0x68, 0x46, 0x29, 0xf9,
	0x9b, 0xa9, 0xf8, 0xe3, 0x3a, 0xa2, 0xb7, 0x07, 0x58, 0xae, 0x97, 0x77, 0x8c, 0x0b, 0x38, 0xdb,
	0x3d, 0x0e, 0x7f, 0x3d, 0xfe, 0x3c, 0x71, 0xba, 0x68, 0x3e, 0x26, 0x6d, 0xff, 0xcf, 0xe1, 0xec,
	0x66, 0x77, 0xe7, 0x34, 0x7d, 0xcf, 0xa6, 0xc2, 0xef, 0x2e, 0xf5, 0xe1, 0x5f, 0x00, 0x00, 0x00,
	0xff, 0xff, 0xd9, 0x86, 0x01, 0x1c, 0x6e, 0x03, 0x00, 0x00,
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6

// EspressoClient is the client API for Espresso service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type EspressoClient interface {
	BoilerTemperature(ctx context.Context, in *TemperatureStreamRequest, opts ...grpc.CallOption) (Espresso_BoilerTemperatureClient, error)
	GetConfiguration(ctx context.Context, in *GetConfigurationRequest, opts ...grpc.CallOption) (*Configuration, error)
	SetConfiguration(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error)
}

type espressoClient struct {
	cc grpc.ClientConnInterface
}

func NewEspressoClient(cc grpc.ClientConnInterface) EspressoClient {
	return &espressoClient{cc}
}

func (c *espressoClient) BoilerTemperature(ctx context.Context, in *TemperatureStreamRequest, opts ...grpc.CallOption) (Espresso_BoilerTemperatureClient, error) {
	stream, err := c.cc.NewStream(ctx, &_Espresso_serviceDesc.Streams[0], "/espressopb.Espresso/BoilerTemperature", opts...)
	if err != nil {
		return nil, err
	}
	x := &espressoBoilerTemperatureClient{stream}
	if err := x.ClientStream.SendMsg(in); err != nil {
		return nil, err
	}
	if err := x.ClientStream.CloseSend(); err != nil {
		return nil, err
	}
	return x, nil
}

type Espresso_BoilerTemperatureClient interface {
	Recv() (*TemperatureStreamResponse, error)
	grpc.ClientStream
}

type espressoBoilerTemperatureClient struct {
	grpc.ClientStream
}

func (x *espressoBoilerTemperatureClient) Recv() (*TemperatureStreamResponse, error) {
	m := new(TemperatureStreamResponse)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

func (c *espressoClient) GetConfiguration(ctx context.Context, in *GetConfigurationRequest, opts ...grpc.CallOption) (*Configuration, error) {
	out := new(Configuration)
	err := c.cc.Invoke(ctx, "/espressopb.Espresso/GetConfiguration", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (c *espressoClient) SetConfiguration(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) {
	out := new(Configuration)
	err := c.cc.Invoke(ctx, "/espressopb.Espresso/SetConfiguration", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// EspressoServer is the server API for Espresso service.
type EspressoServer interface {
	BoilerTemperature(*TemperatureStreamRequest, Espresso_BoilerTemperatureServer) error
	GetConfiguration(context.Context, *GetConfigurationRequest) (*Configuration, error)
	SetConfiguration(context.Context, *Configuration) (*Configuration, error)
}

// UnimplementedEspressoServer can be embedded to have forward compatible implementations.
type UnimplementedEspressoServer struct {
}

func (*UnimplementedEspressoServer) BoilerTemperature(req *TemperatureStreamRequest, srv Espresso_BoilerTemperatureServer) error {
	return status.Errorf(codes.Unimplemented, "method BoilerTemperature not implemented")
}
func (*UnimplementedEspressoServer) GetConfiguration(ctx context.Context, req *GetConfigurationRequest) (*Configuration, error) {
	return nil, status.Errorf(codes.Unimplemented, "method GetConfiguration not implemented")
}
func (*UnimplementedEspressoServer) SetConfiguration(ctx context.Context, req *Configuration) (*Configuration, error) {
	return nil, status.Errorf(codes.Unimplemented, "method SetConfiguration not implemented")
}

func RegisterEspressoServer(s *grpc.Server, srv EspressoServer) {
	s.RegisterService(&_Espresso_serviceDesc, srv)
}

func _Espresso_BoilerTemperature_Handler(srv interface{}, stream grpc.ServerStream) error {
	m := new(TemperatureStreamRequest)
	if err := stream.RecvMsg(m); err != nil {
		return err
	}
	return srv.(EspressoServer).BoilerTemperature(m, &espressoBoilerTemperatureServer{stream})
}

type Espresso_BoilerTemperatureServer interface {
	Send(*TemperatureStreamResponse) error
	grpc.ServerStream
}

type espressoBoilerTemperatureServer struct {
	grpc.ServerStream
}

func (x *espressoBoilerTemperatureServer) Send(m *TemperatureStreamResponse) error {
	return x.ServerStream.SendMsg(m)
}

func _Espresso_GetConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(GetConfigurationRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(EspressoServer).GetConfiguration(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/espressopb.Espresso/GetConfiguration",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(EspressoServer).GetConfiguration(ctx, req.(*GetConfigurationRequest))
	}
	return interceptor(ctx, in, info, handler)
}

func _Espresso_SetConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(Configuration)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(EspressoServer).SetConfiguration(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/espressopb.Espresso/SetConfiguration",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(EspressoServer).SetConfiguration(ctx, req.(*Configuration))
	}
	return interceptor(ctx, in, info, handler)
}

var _Espresso_serviceDesc = grpc.ServiceDesc{
	ServiceName: "espressopb.Espresso",
	HandlerType: (*EspressoServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "GetConfiguration",
			Handler:    _Espresso_GetConfiguration_Handler,
		},
		{
			MethodName: "SetConfiguration",
			Handler:    _Espresso_SetConfiguration_Handler,
		},
	},
	Streams: []grpc.StreamDesc{
		{
			StreamName:    "BoilerTemperature",
			Handler:       _Espresso_BoilerTemperature_Handler,
			ServerStreams: true,
		},
	},
	Metadata: "espresso.proto",
}


<|start_filename|>cmd/espresso/log/logger.go<|end_filename|>
// Package log provides cli-appropriate logging capabilities
package log

import (
	"os"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.SugaredLogger
var minLevel = zapcore.InfoLevel

func init() {
	isErrLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
		return level >= zapcore.ErrorLevel
	})

	isNonErrLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
		return minLevel <= level && level < zapcore.ErrorLevel
	})

	stdoutWriteSyncer := zapcore.Lock(os.Stdout)
	stderrWriteSyncer := zapcore.Lock(os.Stderr)

	config := zap.NewDevelopmentEncoderConfig()
	config.EncodeLevel = zapcore.CapitalColorLevelEncoder
	config.EncodeTime = nil
	config.EncodeLevel = nil
	consoleEncoder := zapcore.NewConsoleEncoder(config)

	core := zapcore.NewTee(
		zapcore.NewCore(consoleEncoder, stdoutWriteSyncer, isErrLevel),
		zapcore.NewCore(consoleEncoder, stderrWriteSyncer, isNonErrLevel),
	)

	log = zap.New(core).Sugar()
}

func Debug(msg string, args ...interface{}) {
	log.Debugf(msg, args...)
}

func Info(msg string, args ...interface{}) {
	log.Infof(msg, args...)
}

func Warn(msg string, args ...interface{}) {
	log.Warnf(msg, args...)
}

func Error(msg string, args ...interface{}) {
	log.Errorf(msg, args...)
}

func Fatal(msg string, args ...interface{}) {
	log.Fatalf(msg, args...)
}

func SetVerbose() {
	minLevel = zapcore.DebugLevel
}

func UnsetVerbose() {
	minLevel = zapcore.InfoLevel
}

func Sync() {
	log.Sync()
}


<|start_filename|>internal/fifo/fifo_test.go<|end_filename|>
package fifo

import (
	"testing"
)

func TestFIFO_Sum(t *testing.T) {
	tests := []struct {
		name string
		data []float32
		want float32
	}{
		{
			name: "Sum empty fifo",
			data: []float32{},
			want: 0.0,
		},
		{
			name: "Sum single val fifo",
			data: []float32{1.2},
			want: 1.2,
		},
		{
			name: "Sum multiple val fifo",
			data: []float32{1.2, 3.4},
			want: 4.6,
		},
		{
			name: "Sum many vals fifo",
			data: []float32{1, 2, 3, 4, 5, 6},
			want: 20,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := NewFIFO(5)
			for _, v := range tt.data {
				f.Push(v)
			}
			if got := f.Sum(); got != tt.want {
				t.Errorf("FIFO.Sum() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestFIFO_Average(t *testing.T) {
	tests := []struct {
		name string
		data []float32
		want float32
	}{
		{
			name: "Average empty fifo",
			data: []float32{},
			want: 0.0,
		},
		{
			name: "Average single val fifo",
			data: []float32{1.2},
			want: 1.2,
		},
		{
			name: "Average multiple val fifo",
			data: []float32{1.2, 3.4},
			want: 2.3,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := NewFIFO(5)
			for _, v := range tt.data {
				f.Push(v)
			}
			if got := f.Average(); got != tt.want {
				t.Errorf("FIFO.Average() = %v, want %v", got, tt.want)
			}
		})
	}
}


<|start_filename|>internal/log/log.go<|end_filename|>
package log

import (
	"os"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

// The main logger from which all child loggers should be derived.
var Logger *zap.Logger

// A convenience logger that is used locally for shorthand logging. It has a
// caller skip of 1 and should not be used elsewhere.
var log *zap.Logger

// init initializes the logger to be useable even if UseDevLogger() or
// UseProdLogger() are never called, e.g. in tests.
func init() {
	log = zap.L()
	Logger = log
}

func Debug(msg string, fields ...zap.Field) {
	log.Debug(msg, fields...)
}

func Info(msg string, fields ...zap.Field) {
	log.Info(msg, fields...)
}

func Warn(msg string, fields ...zap.Field) {
	log.Warn(msg, fields...)
}

func Error(msg string, fields ...zap.Field) {
	log.Error(msg, fields...)
}

func Fatal(msg string, fields ...zap.Field) {
	log.Fatal(msg, fields...)
}

func DPanic(msg string, fields ...zap.Field) {
	log.DPanic(msg, fields...)
}

func UseProdLogger(filePath string, maxFileSizeMB int, maxFileAgeDays int, maxBackups int) {
	encoderConf := zap.NewProductionEncoderConfig()
	encoderConf.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConf.EncodeDuration = millisDurationEncoder
	encoder := zapcore.NewJSONEncoder(encoderConf)

	var writers []zapcore.WriteSyncer
	writers = append(writers, os.Stdout)
	if filePath != "" {
		writers = append(writers, zapcore.AddSync(&lumberjack.Logger{
			Filename:   filePath,
			MaxSize:    maxFileSizeMB,
			MaxAge:     maxFileAgeDays,
			MaxBackups: maxBackups,
		}))
	}

	core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writers...), zapcore.InfoLevel)
	Logger = zap.New(core)
	log = Logger.WithOptions(zap.AddCallerSkip(1))
}

func UseDevLogger() {
	cfg := zap.NewDevelopmentConfig()
	cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
	cfg.DisableStacktrace = true

	Logger, _ = cfg.Build()
	log = Logger.WithOptions(zap.AddCallerSkip(1))
	zap.NewStdLog(Logger)
}

type StringMap map[string]string

func (m StringMap) MarshalLogObject(e zapcore.ObjectEncoder) error {
	for k, v := range (map[string]string)(m) {
		e.AddString(k, v)
	}
	return nil
}

// MillisDurationEncoder serializes a time.Duration to a floating-point number
// of milliseconds elapsed.
func millisDurationEncoder(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendFloat64(float64(d) / float64(time.Millisecond))
}


<|start_filename|>internal/helpers/strings.go<|end_filename|>
package helpers

func StringSliceEquals(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	// They are both empty/nil
	if len(a) == 0 {
		return true
	}

	set := map[string]int{}
	for _, i := range a {
		set[i] = set[i] + 1
	}

	for _, i := range b {
		set[i] = set[i] - 1
	}

	for _, i := range set {
		if i != 0 {
			return false
		}
	}

	return true
}

func MapStrings(strs []string, fn func(s string) string) []string {
	res := make([]string, len(strs))
	for idx, s := range strs {
		res[idx] = fn(s)
	}
	return res
}
