+
+
+static int wait_for_token(const struct sr_dev_inst *sdi, char *tokens, int timeout)
+{
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+ gint64 start_time;
+ int i;
+ unsigned char c;
+
+ serial = sdi->conn;
+ devc = sdi->priv;
+ devc->state = ST_INIT;
+ start_time = g_get_monotonic_time() / 1000;
+ while (TRUE) {
+ if (serial_read(serial, &c, 1) != 1)
+ /* Device might have gone away. */
+ return SR_ERR;
+ process_byte(sdi, c, FALSE);
+ if (devc->state != ST_INIT)
+ /* Wait for a whole packet to get processed. */
+ continue;
+ for (i = 0; tokens[i] != -1; i++) {
+ if (devc->token == tokens[i]) {
+ sr_spew("wait_for_token: got token 0x%.2x", devc->token);
+ return SR_OK;
+ }
+ }
+ if (timeout && g_get_monotonic_time() / 1000 - start_time > timeout)
+ return SR_ERR_TIMEOUT;
+ }
+
+ return SR_OK;
+}
+
+/* cmd is the command to send, tokens are the tokens that denote the state
+ * which the command affects. The first token is the desired state. */
+SR_PRIV int cem_dt_885x_toggle(const struct sr_dev_inst *sdi, uint8_t cmd,
+ char *tokens, int timeout)
+{
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+
+ serial = sdi->conn;
+ devc = sdi->priv;
+
+ /* The device doesn't respond to commands very well. The
+ * only thing to do is wait for the token that will confirm
+ * whether the command worked or not, and resend if needed. */
+ while (TRUE) {
+ if (serial_write(serial, (const void *)&cmd, 1) != 1)
+ return SR_ERR;
+ if (wait_for_token(sdi, tokens, timeout) == SR_ERR)
+ return SR_ERR;
+ if (devc->token == tokens[0])
+ /* It worked. */
+ break;
+ }
+
+ return SR_OK;
+}
+
+SR_PRIV gboolean cem_dt_885x_recording_get(const struct sr_dev_inst *sdi,
+ int *state)
+{
+ struct dev_context *devc;
+ char tokens[5];
+
+ devc = sdi->priv;
+ if (devc->recording == -1) {
+ /* Didn't pick up device state yet. */
+ tokens[0] = TOKEN_RECORDING_ON;
+ tokens[1] = TOKEN_RECORDING_OFF;
+ tokens[2] = -1;
+ if (wait_for_token(sdi, tokens, 510) != SR_OK)
+ return SR_ERR;
+ }
+ *state = devc->token == TOKEN_RECORDING_ON;
+
+ return SR_OK;
+}
+
+SR_PRIV int cem_dt_885x_recording_set(const struct sr_dev_inst *sdi,
+ gboolean state)
+{
+ struct dev_context *devc;
+ int ret;
+ char tokens[5];
+
+ devc = sdi->priv;
+
+ /* The toggle below needs the desired state in first position. */
+ if (state) {
+ tokens[0] = TOKEN_RECORDING_ON;
+ tokens[1] = TOKEN_RECORDING_OFF;
+ } else {
+ tokens[0] = TOKEN_RECORDING_OFF;
+ tokens[1] = TOKEN_RECORDING_ON;
+ }
+ tokens[2] = -1;
+
+ if (devc->recording == -1) {
+ /* Didn't pick up device state yet. */
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ if (devc->token == tokens[0])
+ /* Nothing to do. */
+ return SR_OK;
+ } else if (devc->recording == state)
+ /* Nothing to do. */
+ return SR_OK;
+
+ /* Recording state notifications are sent at 2Hz, so allow just over
+ * that, 510ms, for the state to come in. */
+ ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_RECORDING, tokens, 510);
+
+ return ret;
+}
+
+SR_PRIV int cem_dt_885x_weight_freq_get(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ int cur_setting;
+ char tokens[5];
+
+ devc = sdi->priv;
+
+ cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_FREQ_WEIGHT_A | SR_MQFLAG_SPL_FREQ_WEIGHT_C);
+ if (cur_setting == 0) {
+ /* Didn't pick up device state yet. */
+ tokens[0] = TOKEN_WEIGHT_FREQ_A;
+ tokens[1] = TOKEN_WEIGHT_FREQ_C;
+ tokens[2] = -1;
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ if (devc->token == TOKEN_WEIGHT_FREQ_A)
+ return SR_MQFLAG_SPL_FREQ_WEIGHT_A;
+ else
+ return SR_MQFLAG_SPL_FREQ_WEIGHT_C;
+ } else
+ return cur_setting;
+}
+
+SR_PRIV int cem_dt_885x_weight_freq_set(const struct sr_dev_inst *sdi, int freqw)
+{
+ struct dev_context *devc;
+ int cur_setting, ret;
+ char tokens[5];
+
+ devc = sdi->priv;
+
+ cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_FREQ_WEIGHT_A | SR_MQFLAG_SPL_FREQ_WEIGHT_C);
+ if (cur_setting == freqw)
+ /* Already set to this frequency weighting. */
+ return SR_OK;
+
+ /* The toggle below needs the desired state in first position. */
+ if (freqw == SR_MQFLAG_SPL_FREQ_WEIGHT_A) {
+ tokens[0] = TOKEN_WEIGHT_FREQ_A;
+ tokens[1] = TOKEN_WEIGHT_FREQ_C;
+ } else {
+ tokens[0] = TOKEN_WEIGHT_FREQ_C;
+ tokens[1] = TOKEN_WEIGHT_FREQ_A;
+ }
+ tokens[2] = -1;
+
+ if (cur_setting == 0) {
+ /* Didn't pick up device state yet. */
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ if (devc->token == tokens[0])
+ /* Nothing to do. */
+ return SR_OK;
+ }
+
+ /* 10ms timeout seems to work best for this. */
+ ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_WEIGHT_FREQ, tokens, 10);
+
+ return ret;
+}
+
+SR_PRIV int cem_dt_885x_weight_time_get(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ int cur_setting;
+ char tokens[5];
+
+ devc = sdi->priv;
+
+ cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_TIME_WEIGHT_F | SR_MQFLAG_SPL_TIME_WEIGHT_S);
+ if (cur_setting == 0) {
+ /* Didn't pick up device state yet. */
+ tokens[0] = TOKEN_WEIGHT_TIME_FAST;
+ tokens[1] = TOKEN_WEIGHT_TIME_SLOW;
+ tokens[2] = -1;
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ if (devc->token == TOKEN_WEIGHT_TIME_FAST)
+ return SR_MQFLAG_SPL_TIME_WEIGHT_F;
+ else
+ return SR_MQFLAG_SPL_TIME_WEIGHT_S;
+ } else
+ return cur_setting;
+}
+
+SR_PRIV int cem_dt_885x_weight_time_set(const struct sr_dev_inst *sdi, int timew)
+{
+ struct dev_context *devc;
+ int cur_setting, ret;
+ char tokens[5];
+
+ devc = sdi->priv;
+
+ cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_TIME_WEIGHT_F | SR_MQFLAG_SPL_TIME_WEIGHT_S);
+ if (cur_setting == timew)
+ /* Already set to this time weighting. */
+ return SR_OK;
+
+ /* The toggle below needs the desired state in first position. */
+ if (timew == SR_MQFLAG_SPL_TIME_WEIGHT_F) {
+ tokens[0] = TOKEN_WEIGHT_TIME_FAST;
+ tokens[1] = TOKEN_WEIGHT_TIME_SLOW;
+ } else {
+ tokens[0] = TOKEN_WEIGHT_TIME_SLOW;
+ tokens[1] = TOKEN_WEIGHT_TIME_FAST;
+ }
+ tokens[2] = -1;
+
+ if (cur_setting == 0) {
+ /* Didn't pick up device state yet. */
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ if (devc->token == tokens[0])
+ /* Nothing to do. */
+ return SR_OK;
+ }
+
+ /* 51ms timeout seems to work best for this. */
+ ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_WEIGHT_TIME, tokens, 51);
+
+ return ret;
+}
+
+SR_PRIV int cem_dt_885x_holdmode_get(const struct sr_dev_inst *sdi,
+ gboolean *holdmode)
+{
+ struct dev_context *devc;
+ char tokens[5];
+
+ devc = sdi->priv;
+
+ if (devc->cur_mqflags == 0) {
+ tokens[0] = TOKEN_HOLD_MAX;
+ tokens[1] = TOKEN_HOLD_MIN;
+ tokens[2] = TOKEN_HOLD_NONE;
+ tokens[3] = -1;
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ if (devc->token == TOKEN_HOLD_MAX)
+ devc->cur_mqflags = SR_MQFLAG_MAX;
+ else if (devc->token == TOKEN_HOLD_MIN)
+ devc->cur_mqflags = SR_MQFLAG_MIN;
+ }
+ *holdmode = devc->cur_mqflags & (SR_MQFLAG_MAX | SR_MQFLAG_MIN);
+
+ return SR_OK;
+}
+
+SR_PRIV int cem_dt_885x_holdmode_set(const struct sr_dev_inst *sdi, int holdmode)
+{
+ struct dev_context *devc;
+ int cur_setting, ret;
+ char tokens[5];
+
+ devc = sdi->priv;
+
+ /* The toggle below needs the desired state in first position. */
+ if (holdmode == SR_MQFLAG_MAX) {
+ tokens[0] = TOKEN_HOLD_MAX;
+ tokens[1] = TOKEN_HOLD_MIN;
+ tokens[2] = TOKEN_HOLD_NONE;
+ } else if (holdmode == SR_MQFLAG_MIN) {
+ tokens[0] = TOKEN_HOLD_MIN;
+ tokens[1] = TOKEN_HOLD_MAX;
+ tokens[2] = TOKEN_HOLD_NONE;
+ } else {
+ tokens[0] = TOKEN_HOLD_NONE;
+ tokens[1] = TOKEN_HOLD_MAX;
+ tokens[2] = TOKEN_HOLD_MIN;
+ }
+ tokens[3] = -1;
+
+ if (devc->cur_mqflags == 0) {
+ /* Didn't pick up device state yet. */
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ if (devc->token == tokens[0])
+ /* Nothing to do. */
+ return SR_OK;
+ } else {
+ cur_setting = devc->cur_mqflags & (SR_MQFLAG_MAX | SR_MQFLAG_MIN);
+ if (cur_setting == holdmode)
+ /* Already set correctly. */
+ return SR_OK;
+ }
+
+ /* 51ms timeout seems to work best for this. */
+ ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_HOLD_MAX_MIN, tokens, 51);
+
+ return ret;
+}
+
+SR_PRIV int cem_dt_885x_meas_range_get(const struct sr_dev_inst *sdi,
+ uint64_t *low, uint64_t *high)
+{
+ struct dev_context *devc;
+ char tokens[5];
+
+ devc = sdi->priv;
+ if (devc->cur_meas_range == 0) {
+ tokens[0] = TOKEN_MEAS_RANGE_30_130;
+ tokens[1] = TOKEN_MEAS_RANGE_30_80;
+ tokens[2] = TOKEN_MEAS_RANGE_50_100;
+ tokens[3] = TOKEN_MEAS_RANGE_80_130;
+ tokens[4] = -1;
+ if (wait_for_token(sdi, tokens, 0) != SR_OK)
+ return SR_ERR;
+ devc->cur_meas_range = devc->token;
+ }
+
+ switch (devc->cur_meas_range) {
+ case TOKEN_MEAS_RANGE_30_130:
+ *low = 30;
+ *high = 130;
+ break;
+ case TOKEN_MEAS_RANGE_30_80:
+ *low = 30;
+ *high = 80;
+ break;
+ case TOKEN_MEAS_RANGE_50_100:
+ *low = 50;
+ *high = 100;
+ break;
+ case TOKEN_MEAS_RANGE_80_130:
+ *low = 80;
+ *high = 130;
+ break;
+ default:
+ return SR_ERR;
+ }
+
+ return SR_OK;
+}
+
+SR_PRIV int cem_dt_885x_meas_range_set(const struct sr_dev_inst *sdi,
+ uint64_t low, uint64_t high)
+{
+ struct dev_context *devc;
+ int ret;
+ char token, tokens[6];
+
+ devc = sdi->priv;
+ if (low == 30 && high == 130)
+ token = TOKEN_MEAS_RANGE_30_130;
+ else if (low == 30 && high == 80)
+ token = TOKEN_MEAS_RANGE_30_80;
+ else if (low == 50 && high == 100)
+ token = TOKEN_MEAS_RANGE_50_100;
+ else if (low == 80 && high == 130)
+ token = TOKEN_MEAS_RANGE_80_130;
+ else
+ return SR_ERR;
+
+ sr_dbg("want 0x%.2x", token);
+ /* The toggle below needs the desired state in first position. */
+ tokens[0] = token;
+ tokens[1] = TOKEN_MEAS_RANGE_30_130;
+ tokens[2] = TOKEN_MEAS_RANGE_30_80;
+ tokens[3] = TOKEN_MEAS_RANGE_50_100;
+ tokens[4] = TOKEN_MEAS_RANGE_80_130;
+ tokens[5] = -1;
+
+ if (devc->cur_meas_range == 0) {
+ /* 110ms should be enough for two of these announcements */
+ if (wait_for_token(sdi, tokens, 110) != SR_OK)
+ return SR_ERR;
+ devc->cur_meas_range = devc->token;
+ }
+
+ if (devc->cur_meas_range == token)
+ /* Already set to this range. */
+ return SR_OK;
+
+ /* For measurement range, it works best to ignore announcements of the
+ * current setting and keep resending the toggle quickly. */
+ tokens[1] = -1;
+ ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_MEAS_RANGE, tokens, 11);
+
+ return ret;
+}
+
+SR_PRIV int cem_dt_885x_power_off(const struct sr_dev_inst *sdi)
+{
+ struct sr_serial_dev_inst *serial;
+ char c, cmd;
+
+ serial = sdi->conn;
+
+ /* Reopen the port in non-blocking mode, so we can properly
+ * detect when the device stops communicating. */
+ serial_close(serial);
+ if (serial_open(serial, SERIAL_RDWR | SERIAL_NONBLOCK) != SR_OK)
+ return SR_ERR;
+
+ cmd = CMD_TOGGLE_POWER_OFF;
+ while (TRUE) {
+ serial_flush(serial);
+ if (serial_write(serial, (const void *)&cmd, 1) != 1)
+ return SR_ERR;
+ /* It never takes more than 23ms for the next token to arrive. */
+ g_usleep(25 * 1000);
+ if (serial_read(serial, &c, 1) != 1)
+ /* Device is no longer responding. Good! */
+ break;
+ }
+
+ /* In case the user manually turns on the device again, reset
+ * the port back to blocking. */
+ serial_close(serial);
+ serial_open(serial, SERIAL_RDWR);
+
+ return SR_OK;
+}