// Get the JSON status response for the web server (or later for the M105 command). // Type 1 is the ordinary JSON status response. // Type 2 is the same except that static parameters are also included. // Type 3 is the same but instead of static parameters we report print estimation values. OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) { // Need something to write to... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { // Should never happen return nullptr; } // Machine status char ch = GetStatusCharacter(); response->printf("{\"status\":\"%c\",\"coords\":{", ch); // Coordinates const size_t numAxes = reprap.GetGCodes()->GetNumAxes(); { float liveCoordinates[DRIVES + 1]; #if SUPPORT_ROLAND if (roland->Active()) { roland->GetCurrentRolandPosition(liveCoordinates); } else #endif { move->LiveCoordinates(liveCoordinates, GetCurrentXAxes()); } if (currentTool != nullptr) { const float *offset = currentTool->GetOffset(); for (size_t i = 0; i < numAxes; ++i) { liveCoordinates[i] += offset[i]; } } // Homed axes response->cat("\"axesHomed\":"); ch = '['; for (size_t axis = 0; axis < numAxes; ++axis) { response->catf("%c%d", ch, (gCodes->GetAxisIsHomed(axis)) ? 1 : 0); ch = ','; } // Actual and theoretical extruder positions since power up, last G92 or last M23 response->catf("],\"extr\":"); // announce actual extruder positions ch = '['; for (size_t extruder = 0; extruder < GetExtrudersInUse(); extruder++) { response->catf("%c%.1f", ch, liveCoordinates[numAxes + extruder]); ch = ','; } if (ch == '[') { response->cat(ch); } // XYZ positions response->cat("],\"xyz\":"); if (!gCodes->AllAxesAreHomed() && move->IsDeltaMode()) { // If in Delta mode, skip these coordinates if some axes are not homed response->cat("[0.00,0.00,0.00"); } else { // On Cartesian printers, the live coordinates are (usually) valid ch = '['; for (size_t axis = 0; axis < numAxes; axis++) { response->catf("%c%.3f", ch, liveCoordinates[axis]); ch = ','; } } } // Current tool number const int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number(); response->catf("]},\"currentTool\":%d", toolNumber); // Output - only reported once { bool sendBeep = (beepDuration != 0 && beepFrequency != 0); bool sendMessage = (message[0] != 0); if (sendBeep || sendMessage) { response->cat(",\"output\":{"); // Report beep values if (sendBeep) { response->catf("\"beepDuration\":%d,\"beepFrequency\":%d", beepDuration, beepFrequency); if (sendMessage) { response->cat(","); } beepFrequency = beepDuration = 0; } // Report message if (sendMessage) { response->cat("\"message\":"); response->EncodeString(message, ARRAY_SIZE(message), false); message[0] = 0; } response->cat("}"); } } // Parameters { // ATX power response->catf(",\"params\":{\"atxPower\":%d", platform->AtxPower() ? 1 : 0); // Cooling fan value response->cat(",\"fanPercent\":"); ch = '['; for(size_t i = 0; i < NUM_FANS; i++) { response->catf("%c%.2f", ch, platform->GetFanValue(i) * 100.0); ch = ','; } // Speed and Extrusion factors response->catf("],\"speedFactor\":%.2f,\"extrFactors\":", gCodes->GetSpeedFactor() * 100.0); ch = '['; for (size_t extruder = 0; extruder < GetExtrudersInUse(); extruder++) { response->catf("%c%.2f", ch, gCodes->GetExtrusionFactor(extruder) * 100.0); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); response->catf(",\"babystep\":%.03f}", gCodes->GetBabyStepOffset()); } // G-code reply sequence for webserver (seqence number for AUX is handled later) if (source == ResponseSource::HTTP) { response->catf(",\"seq\":%d", webserver->GetReplySeq()); // There currently appears to be no need for this one, so skip it //response->catf(",\"buff\":%u", webserver->GetGCodeBufferSpace(WebSource::HTTP)); } /* Sensors */ { response->cat(",\"sensors\":{"); // Probe const int v0 = platform->GetZProbeReading(); int v1, v2; switch (platform->GetZProbeSecondaryValues(v1, v2)) { case 1: response->catf("\"probeValue\":%d,\"probeSecondary\":[%d]", v0, v1); break; case 2: response->catf("\"probeValue\":%d,\"probeSecondary\":[%d,%d]", v0, v1, v2); break; default: response->catf("\"probeValue\":%d", v0); break; } // Fan RPM response->catf(",\"fanRPM\":%d}", static_cast<unsigned int>(platform->GetFanRPM())); } /* Temperatures */ { response->cat(",\"temps\":{"); /* Bed */ const int8_t bedHeater = heat->GetBedHeater(); if (bedHeater != -1) { response->catf("\"bed\":{\"current\":%.1f,\"active\":%.1f,\"state\":%d},", heat->GetTemperature(bedHeater), heat->GetActiveTemperature(bedHeater), heat->GetStatus(bedHeater)); } /* Chamber */ const int8_t chamberHeater = heat->GetChamberHeater(); if (chamberHeater != -1) { response->catf("\"chamber\":{\"current\":%.1f,", heat->GetTemperature(chamberHeater)); response->catf("\"active\":%.1f,", heat->GetActiveTemperature(chamberHeater)); response->catf("\"state\":%d},", static_cast<int>(heat->GetStatus(chamberHeater))); } /* Heads */ { response->cat("\"heads\":{\"current\":"); // Current temperatures ch = '['; for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Active temperatures response->catf(",\"active\":"); ch = '['; for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetActiveTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Standby temperatures response->catf(",\"standby\":"); ch = '['; for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetStandbyTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Heater statuses (0=off, 1=standby, 2=active, 3=fault) response->cat(",\"state\":"); ch = '['; for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%d", ch, static_cast<int>(heat->GetStatus(heater))); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); } response->cat("}}"); } // Time since last reset response->catf(",\"time\":%.1f", platform->Time()); #if SUPPORT_SCANNER // Scanner if (scanner->IsEnabled()) { response->catf(",\"scanner\":{\"status\":\"%c\"", scanner->GetStatusCharacter()); response->catf(",\"progress\":%.1f}", scanner->GetProgress()); } #endif /* Extended Status Response */ if (type == 2) { // Cold Extrude/Retract response->catf(",\"coldExtrudeTemp\":%1.f", heat->ColdExtrude() ? 0 : HOT_ENOUGH_TO_EXTRUDE); response->catf(",\"coldRetractTemp\":%1.f", heat->ColdExtrude() ? 0 : HOT_ENOUGH_TO_RETRACT); // Maximum hotend temperature - DWC just wants the highest one response->catf(",\"tempLimit\":%1.f", heat->GetHighestTemperatureLimit()); // Endstops uint16_t endstops = 0; for(size_t drive = 0; drive < DRIVES; drive++) { EndStopHit stopped = platform->Stopped(drive); if (stopped == EndStopHit::highHit || stopped == EndStopHit::lowHit) { endstops |= (1 << drive); } } response->catf(",\"endstops\":%d", endstops); // Firmware name, machine geometry and number of axes response->catf(",\"firmwareName\":\"%s\",\"geometry\":\"%s\",\"axes\":%u", FIRMWARE_NAME, move->GetGeometryString(), numAxes); // Total and mounted volumes size_t mountedCards = 0; for(size_t i = 0; i < NumSdCards; i++) { if (platform->GetMassStorage()->IsDriveMounted(i)) { mountedCards |= (1 << i); } } response->catf(",\"volumes\":%u,\"mountedVolumes\":%u", NumSdCards, mountedCards); // Machine name response->cat(",\"name\":"); response->EncodeString(myName, ARRAY_SIZE(myName), false); /* Probe */ { const ZProbeParameters probeParams = platform->GetCurrentZProbeParameters(); // Trigger threshold response->catf(",\"probe\":{\"threshold\":%d", probeParams.adcValue); // Trigger height response->catf(",\"height\":%.2f", probeParams.height); // Type response->catf(",\"type\":%d}", platform->GetZProbeType()); } /* Tool Mapping */ { response->cat(",\"tools\":["); for(Tool *tool = toolList; tool != nullptr; tool = tool->Next()) { // Heaters response->catf("{\"number\":%d,\"heaters\":[", tool->Number()); for(size_t heater=0; heater<tool->HeaterCount(); heater++) { response->catf("%d", tool->Heater(heater)); if (heater + 1 < tool->HeaterCount()) { response->cat(","); } } // Extruder drives response->cat("],\"drives\":["); for(size_t drive=0; drive<tool->DriveCount(); drive++) { response->catf("%d", tool->Drive(drive)); if (drive + 1 < tool->DriveCount()) { response->cat(","); } } // Axis mapping. Currently we only map the X axis, but we return an array of arrays to allow for mapping other axes in future. response->cat("],\"axisMap\":[["); bool first = true; for (size_t xi = 0; xi < MAX_AXES; ++xi) { if ((tool->GetXAxisMap() & (1u << xi)) != 0) { if (first) { first = false; } else { response->cat(","); } response->catf("%u", xi); } } // Do we have any more tools? if (tool->Next() != nullptr) { response->cat("]]},"); } else { response->cat("]]}"); } } response->cat("]"); } // MCU temperatures #ifndef __RADDS__ { float minT, currT, maxT; platform->GetMcuTemperatures(minT, currT, maxT); response->catf(",\"mcutemp\":{\"min\":%.1f,\"cur\":%.1f,\"max\":%.1f}", minT, currT, maxT); } #endif #ifdef DUET_NG // Power in voltages { float minV, currV, maxV; platform->GetPowerVoltages(minV, currV, maxV); response->catf(",\"vin\":{\"min\":%.1f,\"cur\":%.1f,\"max\":%.1f}", minV, currV, maxV); } #endif } else if (type == 3) { // Current Layer response->catf(",\"currentLayer\":%d", printMonitor->GetCurrentLayer()); // Current Layer Time response->catf(",\"currentLayerTime\":%.1f", printMonitor->GetCurrentLayerTime()); // Raw Extruder Positions response->cat(",\"extrRaw\":"); ch = '['; for (size_t extruder = 0; extruder < GetExtrudersInUse(); extruder++) // loop through extruders { response->catf("%c%.1f", ch, gCodes->GetRawExtruderTotalByDrive(extruder)); ch = ','; } if (ch == '[') { response->cat(ch); // no extruders } // Fraction of file printed response->catf("],\"fractionPrinted\":%.1f", (printMonitor->IsPrinting()) ? (gCodes->FractionOfFilePrinted() * 100.0) : 0.0); // First Layer Duration response->catf(",\"firstLayerDuration\":%.1f", printMonitor->GetFirstLayerDuration()); // First Layer Height // NB: This shouldn't be needed any more, but leave it here for the case that the file-based first-layer detection fails response->catf(",\"firstLayerHeight\":%.2f", printMonitor->GetFirstLayerHeight()); // Print Duration response->catf(",\"printDuration\":%.1f", printMonitor->GetPrintDuration()); // Warm-Up Time response->catf(",\"warmUpDuration\":%.1f", printMonitor->GetWarmUpDuration()); /* Print Time Estimations */ { // Based on file progress response->catf(",\"timesLeft\":{\"file\":%.1f", printMonitor->EstimateTimeLeft(fileBased)); // Based on filament usage response->catf(",\"filament\":%.1f", printMonitor->EstimateTimeLeft(filamentBased)); // Based on layers response->catf(",\"layer\":%.1f}", printMonitor->EstimateTimeLeft(layerBased)); } } if (source == ResponseSource::AUX) { OutputBuffer *reply = platform->GetAuxGCodeReply(); if (response != nullptr) { // Send the response to the last command. Do this last response->catf(",\"seq\":%u,\"resp\":", platform->GetAuxSeq()); // send the response sequence number // Send the JSON response response->EncodeReply(reply, true); // also releases the OutputBuffer chain } } response->cat("}"); return response; }
// Get the JSON status response for PanelDue or the old web server. // Type 0 was the old-style webserver status response, but is no longer supported. // Type 1 is the new-style webserver status response. // Type 2 is the M105 S2 response, which is like the new-style status response but some fields are omitted. // Type 3 is the M105 S3 response, which is like the M105 S2 response except that static values are also included. // 'seq' is the response sequence number, if it is not -1 and we have a different sequence number then we send the gcode response OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) { // Need something to write to... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { // Should never happen return nullptr; } // Send the status. Note that 'S' has always meant that the machine is halted in this version of the status response, so we use A for pAused. char ch = GetStatusCharacter(); if (ch == 'S') // if paused then send 'A' { ch = 'A'; } else if (ch == 'H') // if halted then send 'S' { ch = 'S'; } response->printf("{\"status\":\"%c\",\"heaters\":", ch); // Send the heater actual temperatures const int8_t bedHeater = heat->GetBedHeater(); if (bedHeater != -1) { ch = ','; response->catf("[%.1f", heat->GetTemperature(bedHeater)); } else { ch = '['; } for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the heater active temperatures response->catf(",\"active\":"); if (heat->GetBedHeater() != -1) { ch = ','; response->catf("[%.1f", heat->GetActiveTemperature(heat->GetBedHeater())); } else { ch = '['; } for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetActiveTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the heater standby temperatures response->catf(",\"standby\":"); if (bedHeater != -1) { ch = ','; response->catf("[%.1f", heat->GetStandbyTemperature(bedHeater)); } else { ch = '['; } for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetStandbyTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the heater statuses (0=off, 1=standby, 2=active, 3 = fault) response->cat(",\"hstat\":"); if (bedHeater != -1) { ch = ','; response->catf("[%d", static_cast<int>(heat->GetStatus(bedHeater))); } else { ch = '['; } for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%d", ch, static_cast<int>(heat->GetStatus(heater))); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send XYZ positions const size_t numAxes = reprap.GetGCodes()->GetNumAxes(); float liveCoordinates[DRIVES]; reprap.GetMove()->LiveCoordinates(liveCoordinates, GetCurrentXAxes()); const Tool* const currentTool = reprap.GetCurrentTool(); if (currentTool != nullptr) { const float *offset = currentTool->GetOffset(); for (size_t i = 0; i < numAxes; ++i) { liveCoordinates[i] += offset[i]; } } response->catf(",\"pos\":"); // announce the XYZ position ch = '['; for (size_t drive = 0; drive < numAxes; drive++) { response->catf("%c%.3f", ch, liveCoordinates[drive]); ch = ','; } // Send extruder total extrusion since power up, last G92 or last M23 response->cat("],\"extr\":"); // announce the extruder positions ch = '['; for (size_t drive = 0; drive < reprap.GetExtrudersInUse(); drive++) // loop through extruders { response->catf("%c%.1f", ch, gCodes->GetRawExtruderPosition(drive)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the speed and extruder override factors response->catf(",\"sfactor\":%.2f,\"efactor\":", gCodes->GetSpeedFactor() * 100.0); ch = '['; for (size_t i = 0; i < reprap.GetExtrudersInUse(); ++i) { response->catf("%c%.2f", ch, gCodes->GetExtrusionFactor(i) * 100.0); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the baby stepping offset response->catf(",\"babystep\":%.03f", gCodes->GetBabyStepOffset()); // Send the current tool number const int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number(); response->catf(",\"tool\":%d", toolNumber); // Send the Z probe value const int v0 = platform->GetZProbeReading(); int v1, v2; switch (platform->GetZProbeSecondaryValues(v1, v2)) { case 1: response->catf(",\"probe\":\"%d (%d)\"", v0, v1); break; case 2: response->catf(",\"probe\":\"%d (%d, %d)\"", v0, v1, v2); break; default: response->catf(",\"probe\":\"%d\"", v0); break; } // Send the fan settings, for PanelDue firmware 1.13 and later response->catf(",\"fanPercent\":"); ch = '['; for (size_t i = 0; i < NUM_FANS; ++i) { response->catf("%c%.02f", ch, platform->GetFanValue(i) * 100.0); ch = ','; } // Send fan RPM value (we only support one) response->catf("],\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM())); // Send the home state. To keep the messages short, we send 1 for homed and 0 for not homed, instead of true and false. response->cat(",\"homed\":"); ch = '['; for (size_t axis = 0; axis < numAxes; ++axis) { response->catf("%c%d", ch, (gCodes->GetAxisIsHomed(axis)) ? 1 : 0); ch = ','; } response->cat(']'); if (printMonitor->IsPrinting()) { // Send the fraction printed response->catf(",\"fraction_printed\":%.4f", max<float>(0.0, gCodes->FractionOfFilePrinted())); } // Short messages are now pushed directly to PanelDue, so don't include them here as well // We no longer send the amount of http buffer space here because the web interface doesn't use these formns of status response if (type == 2) { if (printMonitor->IsPrinting()) { // Send estimated times left based on file progress, filament usage, and layers response->catf(",\"timesLeft\":[%.1f,%.1f,%.1f]", printMonitor->EstimateTimeLeft(fileBased), printMonitor->EstimateTimeLeft(filamentBased), printMonitor->EstimateTimeLeft(layerBased)); } } else if (type == 3) { // Add the static fields response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"volumes\":%u,\"numTools\":%u,\"myName\":", move->GetGeometryString(), numAxes, NumSdCards, GetNumberOfContiguousTools()); response->EncodeString(myName, ARRAY_SIZE(myName), false); response->cat(",\"firmwareName\":"); response->EncodeString(FIRMWARE_NAME, strlen(FIRMWARE_NAME), false); } const int auxSeq = (int)platform->GetAuxSeq(); if (type < 2 || (seq != -1 && auxSeq != seq)) { // Send the response to the last command. Do this last because it can be long and may need to be truncated. response->catf(",\"seq\":%d,\"resp\":", auxSeq); // send the response sequence number // Send the JSON response response->EncodeReply(platform->GetAuxGCodeReply(), true); // also releases the OutputBuffer chain } response->cat("}"); return response; }
// Get the JSON status response for PanelDue or the old web server. // Type 0 was the old-style webserver status response, but is no longer supported. // Type 1 is the new-style webserver status response. // Type 2 is the M105 S2 response, which is like the new-style status response but some fields are omitted. // Type 3 is the M105 S3 response, which is like the M105 S2 response except that static values are also included. // 'seq' is the response sequence number, if it is not -1 and we have a different sequence number then we send the gcode response OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) { // Need something to write to... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { // Should never happen return nullptr; } // Send the status. Note that 'S' has always meant that the machine is halted in this version of the status response, so we use A for pAused. char ch = GetStatusCharacter(); if (ch == 'S') // if paused then send 'A' { ch = 'A'; } else if (ch == 'H') // if halted then send 'S' { ch = 'S'; } response->printf("{\"status\":\"%c\",\"heaters\":", ch); // Send the heater actual temperatures const int8_t bedHeater = heat->GetBedHeater(); if (bedHeater != -1) { ch = ','; response->catf("[%.1f", heat->GetTemperature(bedHeater)); } else { ch = '['; } for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the heater active temperatures response->catf(",\"active\":"); if (heat->GetBedHeater() != -1) { ch = ','; response->catf("[%.1f", heat->GetActiveTemperature(heat->GetBedHeater())); } else { ch = '['; } for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetActiveTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the heater standby temperatures response->catf(",\"standby\":"); if (bedHeater != -1) { ch = ','; response->catf("[%.1f", heat->GetStandbyTemperature(bedHeater)); } else { ch = '['; } for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetStandbyTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the heater statuses (0=off, 1=standby, 2=active) response->cat(",\"hstat\":"); if (bedHeater != -1) { ch = ','; response->catf("[%d", static_cast<int>(heat->GetStatus(bedHeater))); } else { ch = '['; } for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%d", ch, static_cast<int>(heat->GetStatus(heater))); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send XYZ positions float liveCoordinates[DRIVES]; reprap.GetMove()->LiveCoordinates(liveCoordinates); const Tool* currentTool = reprap.GetCurrentTool(); if (currentTool != nullptr) { const float *offset = currentTool->GetOffset(); for (size_t i = 0; i < AXES; ++i) { liveCoordinates[i] += offset[i]; } } response->catf(",\"pos\":"); // announce the XYZ position ch = '['; for (size_t drive = 0; drive < AXES; drive++) { response->catf("%c%.2f", ch, liveCoordinates[drive]); ch = ','; } // Send extruder total extrusion since power up, last G92 or last M23 response->cat("],\"extr\":"); // announce the extruder positions ch = '['; for (size_t drive = 0; drive < reprap.GetExtrudersInUse(); drive++) // loop through extruders { response->catf("%c%.1f", ch, gCodes->GetRawExtruderPosition(drive)); ch = ','; } response->cat((ch == ']') ? "[]" : "]"); // Send the speed and extruder override factors response->catf(",\"sfactor\":%.2f,\"efactor\":", gCodes->GetSpeedFactor() * 100.0); ch = '['; for (size_t i = 0; i < reprap.GetExtrudersInUse(); ++i) { response->catf("%c%.2f", ch, gCodes->GetExtrusionFactor(i) * 100.0); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Send the current tool number int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number(); response->catf(",\"tool\":%d", toolNumber); // Send the Z probe value int v0 = platform->ZProbe(); int v1, v2; switch (platform->GetZProbeSecondaryValues(v1, v2)) { case 1: response->catf(",\"probe\":\"%d (%d)\"", v0, v1); break; case 2: response->catf(",\"probe\":\"%d (%d, %d)\"", v0, v1, v2); break; default: response->catf(",\"probe\":\"%d\"", v0); break; } // Send the fan0 settings (for PanelDue firmware 1.13) response->catf(",\"fanPercent\":[%.02f,%.02f]", platform->GetFanValue(0) * 100.0, platform->GetFanValue(1) * 100.0); // Send fan RPM value response->catf(",\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM())); // Send the home state. To keep the messages short, we send 1 for homed and 0 for not homed, instead of true and false. if (type != 0) { response->catf(",\"homed\":[%d,%d,%d]", (gCodes->GetAxisIsHomed(0)) ? 1 : 0, (gCodes->GetAxisIsHomed(1)) ? 1 : 0, (gCodes->GetAxisIsHomed(2)) ? 1 : 0); } else { response->catf(",\"hx\":%d,\"hy\":%d,\"hz\":%d", (gCodes->GetAxisIsHomed(0)) ? 1 : 0, (gCodes->GetAxisIsHomed(1)) ? 1 : 0, (gCodes->GetAxisIsHomed(2)) ? 1 : 0); } if (printMonitor->IsPrinting()) { // Send the fraction printed response->catf(",\"fraction_printed\":%.4f", max<float>(0.0, gCodes->FractionOfFilePrinted())); } response->cat(",\"message\":"); response->EncodeString(message, ARRAY_SIZE(message), false); if (type < 2) { response->catf(",\"buff\":%u", webserver->GetGCodeBufferSpace(WebSource::HTTP)); // send the amount of buffer space available for gcodes } else if (type == 2) { if (printMonitor->IsPrinting()) { // Send estimated times left based on file progress, filament usage, and layers response->catf(",\"timesLeft\":[%.1f,%.1f,%.1f]", printMonitor->EstimateTimeLeft(fileBased), printMonitor->EstimateTimeLeft(filamentBased), printMonitor->EstimateTimeLeft(layerBased)); } } else if (type == 3) { // Add the static fields. For now this is just geometry and the machine name, but other fields could be added e.g. axis lengths. response->catf(",\"geometry\":\"%s\",\"myName\":", move->GetGeometryString()); response->EncodeString(myName, ARRAY_SIZE(myName), false); } int auxSeq = (int)gCodes->GetAuxSeq(); if (type < 2 || (seq != -1 && (int)auxSeq != seq)) { // Send the response to the last command. Do this last because it can be long and may need to be truncated. response->catf(",\"seq\":%u,\"resp\":", auxSeq); // send the response sequence number // Send the JSON response response->EncodeReply(gCodes->GetAuxGCodeReply(), true); // also releases the OutputBuffer chain } response->cat("}"); return response; }
// Get the JSON status response for the web server (or later for the M105 command). // Type 1 is the ordinary JSON status response. // Type 2 is the same except that static parameters are also included. // Type 3 is the same but instead of static parameters we report print estimation values. OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) { // Need something to write to... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { // Should never happen return nullptr; } // Machine status char ch = GetStatusCharacter(); response->printf("{\"status\":\"%c\",\"coords\":{", ch); /* Coordinates */ { float liveCoordinates[DRIVES + 1]; #if SUPPORT_ROLAND if (roland->Active()) { roland->GetCurrentRolandPosition(liveCoordinates); } else #endif { move->LiveCoordinates(liveCoordinates); } if (currentTool != nullptr) { const float *offset = currentTool->GetOffset(); for (size_t i = 0; i < AXES; ++i) { liveCoordinates[i] += offset[i]; } } // Homed axes response->catf("\"axesHomed\":[%d,%d,%d]", (gCodes->GetAxisIsHomed(0)) ? 1 : 0, (gCodes->GetAxisIsHomed(1)) ? 1 : 0, (gCodes->GetAxisIsHomed(2)) ? 1 : 0); // Actual and theoretical extruder positions since power up, last G92 or last M23 response->catf(",\"extr\":"); // announce actual extruder positions ch = '['; for (size_t extruder = 0; extruder < GetExtrudersInUse(); extruder++) { response->catf("%c%.1f", ch, liveCoordinates[AXES + extruder]); ch = ','; } if (ch == '[') { response->cat("["); } // XYZ positions response->cat("],\"xyz\":"); if (!gCodes->AllAxesAreHomed() && move->IsDeltaMode()) { // If in Delta mode, skip these coordinates if some axes are not homed response->cat("[0.00,0.00,0.00"); } else { // On Cartesian printers, the live coordinates are (usually) valid ch = '['; for (size_t axis = 0; axis < AXES; axis++) { response->catf("%c%.2f", ch, liveCoordinates[axis]); ch = ','; } } } // Current tool number int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number(); response->catf("]},\"currentTool\":%d", toolNumber); /* Output - only reported once */ { bool sendBeep = (beepDuration != 0 && beepFrequency != 0); bool sendMessage = (message[0] != 0); bool sourceRight = (gCodes->HaveAux() && source == ResponseSource::AUX) || (!gCodes->HaveAux() && source == ResponseSource::HTTP); if ((sendBeep || message[0] != 0) && sourceRight) { response->cat(",\"output\":{"); // Report beep values if (sendBeep) { response->catf("\"beepDuration\":%d,\"beepFrequency\":%d", beepDuration, beepFrequency); if (sendMessage) { response->cat(","); } beepFrequency = beepDuration = 0; } // Report message if (sendMessage) { response->cat("\"message\":"); response->EncodeString(message, ARRAY_SIZE(message), false); message[0] = 0; } response->cat("}"); } } /* Parameters */ { // ATX power response->catf(",\"params\":{\"atxPower\":%d", platform->AtxPower() ? 1 : 0); // Cooling fan value response->cat(",\"fanPercent\":["); for(size_t i = 0; i < NUM_FANS; i++) { if (i == NUM_FANS - 1) { response->catf("%.2f", platform->GetFanValue(i) * 100.0); } else { response->catf("%.2f,", platform->GetFanValue(i) * 100.0); } } // Speed and Extrusion factors response->catf("],\"speedFactor\":%.2f,\"extrFactors\":", gCodes->GetSpeedFactor() * 100.0); ch = '['; for (size_t extruder = 0; extruder < GetExtrudersInUse(); extruder++) { response->catf("%c%.2f", ch, gCodes->GetExtrusionFactor(extruder) * 100.0); ch = ','; } response->cat((ch == '[') ? "[]}" : "]}"); } // G-code reply sequence for webserver (seqence number for AUX is handled later) if (source == ResponseSource::HTTP) { response->catf(",\"seq\":%d", webserver->GetReplySeq()); // There currently appears to be no need for this one, so skip it //response->catf(",\"buff\":%u", webserver->GetGCodeBufferSpace(WebSource::HTTP)); } /* Sensors */ { response->cat(",\"sensors\":{"); // Probe int v0 = platform->ZProbe(); int v1, v2; switch (platform->GetZProbeSecondaryValues(v1, v2)) { case 1: response->catf("\"probeValue\":%d,\"probeSecondary\":[%d]", v0, v1); break; case 2: response->catf("\"probeValue\":%d,\"probeSecondary\":[%d,%d]", v0, v1, v2); break; default: response->catf("\"probeValue\":%d", v0); break; } // Fan RPM response->catf(",\"fanRPM\":%d}", static_cast<unsigned int>(platform->GetFanRPM())); } /* Temperatures */ { response->cat(",\"temps\":{"); /* Bed */ const int8_t bedHeater = heat->GetBedHeater(); if (bedHeater != -1) { response->catf("\"bed\":{\"current\":%.1f,\"active\":%.1f,\"state\":%d},", heat->GetTemperature(bedHeater), heat->GetActiveTemperature(bedHeater), heat->GetStatus(bedHeater)); } /* Chamber */ const int8_t chamberHeater = heat->GetChamberHeater(); if (chamberHeater != -1) { response->catf("\"chamber\":{\"current\":%.1f,", heat->GetTemperature(chamberHeater)); response->catf("\"active\":%.1f,", heat->GetActiveTemperature(chamberHeater)); response->catf("\"state\":%d},", static_cast<int>(heat->GetStatus(chamberHeater))); } /* Heads */ { response->cat("\"heads\":{\"current\":"); // Current temperatures ch = '['; for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Active temperatures response->catf(",\"active\":"); ch = '['; for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetActiveTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Standby temperatures response->catf(",\"standby\":"); ch = '['; for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%.1f", ch, heat->GetStandbyTemperature(heater)); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); // Heater statuses (0=off, 1=standby, 2=active, 3=fault) response->cat(",\"state\":"); ch = '['; for (size_t heater = E0_HEATER; heater < GetToolHeatersInUse(); heater++) { response->catf("%c%d", ch, static_cast<int>(heat->GetStatus(heater))); ch = ','; } response->cat((ch == '[') ? "[]" : "]"); } response->cat("}}"); } // Time since last reset response->catf(",\"time\":%.1f", platform->Time()); /* Extended Status Response */ if (type == 2) { // Cold Extrude/Retract response->catf(",\"coldExtrudeTemp\":%1.f", heat->ColdExtrude() ? 0 : HOT_ENOUGH_TO_EXTRUDE); response->catf(",\"coldRetractTemp\":%1.f", heat->ColdExtrude() ? 0 : HOT_ENOUGH_TO_RETRACT); // Endstops uint16_t endstops = 0; for(size_t drive = 0; drive < DRIVES; drive++) { EndStopHit stopped = platform->Stopped(drive); if (stopped == EndStopHit::highHit || stopped == EndStopHit::lowHit) { endstops |= (1 << drive); } } response->catf(",\"endstops\":%d", endstops); // Delta configuration response->catf(",\"geometry\":\"%s\"", move->GetGeometryString()); // Machine name response->cat(",\"name\":"); response->EncodeString(myName, ARRAY_SIZE(myName), false); /* Probe */ { const ZProbeParameters probeParams = platform->GetZProbeParameters(); // Trigger threshold response->catf(",\"probe\":{\"threshold\":%d", probeParams.adcValue); // Trigger height response->catf(",\"height\":%.2f", probeParams.height); // Type response->catf(",\"type\":%d}", platform->GetZProbeType()); } /* Tool Mapping */ { response->cat(",\"tools\":["); for(Tool *tool = toolList; tool != nullptr; tool = tool->Next()) { // Heaters response->catf("{\"number\":%d,\"heaters\":[", tool->Number()); for(size_t heater=0; heater<tool->HeaterCount(); heater++) { response->catf("%d", tool->Heater(heater)); if (heater + 1 < tool->HeaterCount()) { response->cat(","); } } // Extruder drives response->cat("],\"drives\":["); for(size_t drive=0; drive<tool->DriveCount(); drive++) { response->catf("%d", tool->Drive(drive)); if (drive + 1 < tool->DriveCount()) { response->cat(","); } } // Do we have any more tools? if (tool->Next() != nullptr) { response->cat("]},"); } else { response->cat("]}"); } } response->cat("]"); } } else if (type == 3) { // Current Layer response->catf(",\"currentLayer\":%d", printMonitor->GetCurrentLayer()); // Current Layer Time response->catf(",\"currentLayerTime\":%.1f", printMonitor->GetCurrentLayerTime()); // Raw Extruder Positions response->cat(",\"extrRaw\":"); ch = '['; for (size_t extruder = 0; extruder < GetExtrudersInUse(); extruder++) // loop through extruders { response->catf("%c%.1f", ch, gCodes->GetRawExtruderTotalByDrive(extruder)); ch = ','; } if (ch == '[') { response->cat("]"); } // Fraction of file printed response->catf("],\"fractionPrinted\":%.1f", (printMonitor->IsPrinting()) ? (gCodes->FractionOfFilePrinted() * 100.0) : 0.0); // First Layer Duration response->catf(",\"firstLayerDuration\":%.1f", printMonitor->GetFirstLayerDuration()); // First Layer Height // NB: This shouldn't be needed any more, but leave it here for the case that the file-based first-layer detection fails response->catf(",\"firstLayerHeight\":%.2f", printMonitor->GetFirstLayerHeight()); // Print Duration response->catf(",\"printDuration\":%.1f", printMonitor->GetPrintDuration()); // Warm-Up Time response->catf(",\"warmUpDuration\":%.1f", printMonitor->GetWarmUpDuration()); /* Print Time Estimations */ { // Based on file progress response->catf(",\"timesLeft\":{\"file\":%.1f", printMonitor->EstimateTimeLeft(fileBased)); // Based on filament usage response->catf(",\"filament\":%.1f", printMonitor->EstimateTimeLeft(filamentBased)); // Based on layers response->catf(",\"layer\":%.1f}", printMonitor->EstimateTimeLeft(layerBased)); } } if (source == ResponseSource::AUX) { OutputBuffer *response = gCodes->GetAuxGCodeReply(); if (response != nullptr) { // Send the response to the last command. Do this last response->catf(",\"seq\":%u,\"resp\":", gCodes->GetAuxSeq()); // send the response sequence number // Send the JSON response response->EncodeReply(response, true); // also releases the OutputBuffer chain } } response->cat("}"); return response; }
// Process the first fragment of input from the client. // Return true if the session should be kept open. bool Webserver::ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment) { // Process connect messages first if (StringEquals(command, "connect") && GetKeyValue("password") != nullptr) { OutputBuffer *response; if (OutputBuffer::Allocate(response)) { if (session.isAuthenticated || reprap.CheckPassword(GetKeyValue("password"))) { // Password is OK, see if we can update the current RTC date and time const char *timeVal = GetKeyValue("time"); if (timeVal != nullptr && !platform->IsDateTimeSet()) { struct tm timeInfo; memset(&timeInfo, 0, sizeof(timeInfo)); if (strptime(timeVal, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr) { time_t newTime = mktime(&timeInfo); platform->SetDateTime(newTime); } } // Client has logged in session.isAuthenticated = true; response->printf("{\"err\":0,\"sessionTimeout\":%u,\"boardType\":\"%s\"}", httpSessionTimeout, platform->GetBoardString()); } else { // Wrong password response->copy("{\"err\":1}"); } network->SendReply(session.ip, 200 | rcJson, response); } else { // If we failed to allocate an output buffer, send back an error string network->SendReply(session.ip, 200 | rcJson, "{\"err\":2}"); } return false; } if (StringEquals(command, "disconnect")) { network->SendReply(session.ip, 200 | rcJson, "{\"err\":0}"); DeleteSession(session.ip); return false; } // Try to authorise the user automatically to retain compatibility with the old web interface if (!session.isAuthenticated && reprap.NoPasswordSet()) { session.isAuthenticated = true; } // If the client is still not authenticated, stop here if (!session.isAuthenticated) { network->SendReply(session.ip, 500, "Not authorized"); return false; } if (StringEquals(command, "reply")) { SendGCodeReply(session); return false; } // rr_configfile sends the config as plain text well if (StringEquals(command, "configfile")) { const char *configPath = platform->GetMassStorage()->CombineName(platform->GetSysDir(), platform->GetConfigFile()); char fileName[FILENAME_LENGTH]; strncpy(fileName, configPath, FILENAME_LENGTH); SendFile(fileName, session); return false; } if (StringEquals(command, "download") && GetKeyValue("name") != nullptr) { SendFile(GetKeyValue("name"), session); return false; } if (StringEquals(command, "upload")) { const char* nameVal = GetKeyValue("name"); const char* lengthVal = GetKeyValue("length"); const char* timeVal = GetKeyValue("time"); if (nameVal != nullptr && lengthVal != nullptr) { // Try to obtain the last modified time time_t fileLastModified = 0; struct tm timeInfo; memset(&timeInfo, 0, sizeof(timeInfo)); if (timeVal != nullptr && strptime(timeVal, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr) { fileLastModified = mktime(&timeInfo); } // Deal with file upload request uint32_t fileLength = atol(lengthVal); StartUpload(session, nameVal, fileLength, fileLastModified); if (session.uploadState == uploading) { if (isOnlyFragment) { FinishUpload(session); return false; } else { network->DiscardMessage(); // no reply needed yet return true; // expecting more data } } } network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); // TODO return the cause of the error return false; } if (StringEquals(command, "move")) { const char* response = "{\"err\":1}"; // assume failure const char* const oldVal = GetKeyValue("old"); const char* const newVal = GetKeyValue("new"); if (oldVal != nullptr && newVal != nullptr) { const bool success = platform->GetMassStorage()->Rename(oldVal, newVal); if (success) { response = "{\"err\":0}"; } } network->SendReply(session.ip, 200 | rcJson, response); return false; } if (StringEquals(command, "mkdir")) { const char* response = "{\"err\":1}"; // assume failure const char* dirVal = GetKeyValue("dir"); if (dirVal != nullptr) { bool ok = (platform->GetMassStorage()->MakeDirectory(dirVal)); if (ok) { response = "{\"err\":0}"; } } network->SendReply(session.ip, 200 | rcJson, response); return false; } if (StringEquals(command, "delete")) { const char* response = "{\"err\":1}"; // assume failure const char* nameVal = GetKeyValue("name"); if (nameVal != nullptr) { bool ok = platform->GetMassStorage()->Delete("0:/", nameVal); if (ok) { response = "{\"err\":0}"; } } network->SendReply(session.ip, 200 | rcJson, response); return false; } // The remaining commands use an OutputBuffer for the response OutputBuffer *response = nullptr; if (StringEquals(command, "status")) { const char* typeVal = GetKeyValue("type"); if (typeVal != nullptr) { // New-style JSON status responses int type = atoi(typeVal); if (type < 1 || type > 3) { type = 1; } response = reprap.GetStatusResponse(type, ResponseSource::HTTP); } else { response = reprap.GetLegacyStatusResponse(1, 0); } } else if (StringEquals(command, "gcode")) { const char* gcodeVal = GetKeyValue("gcode"); if (gcodeVal != nullptr) { RegularGCodeInput * const httpInput = reprap.GetGCodes()->GetHTTPInput(); httpInput->Put(HTTP_MESSAGE, gcodeVal); if (OutputBuffer::Allocate(response)) { response->printf("{\"buff\":%u}", httpInput->BufferSpaceLeft()); } } else { network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); return false; } } else if (StringEquals(command, "filelist") && GetKeyValue("dir") != nullptr) { response = reprap.GetFilelistResponse(GetKeyValue("dir")); } else if (StringEquals(command, "files")) { const char* dir = GetKeyValue("dir"); if (dir == nullptr) { dir = platform->GetGCodeDir(); } const char* const flagDirsVal = GetKeyValue("flagDirs"); const bool flagDirs = flagDirsVal != nullptr && atoi(flagDirsVal) == 1; response = reprap.GetFilesResponse(dir, flagDirs); } else if (StringEquals(command, "fileinfo")) { const char* const nameVal = GetKeyValue("name"); if (reprap.GetPrintMonitor()->GetFileInfoResponse(nameVal, response)) { processingDeferredRequest = false; } else { processingDeferredRequest = true; } } else if (StringEquals(command, "config")) { response = reprap.GetConfigResponse(); } else { platform->MessageF(HOST_MESSAGE, "KnockOut request: %s not recognised\n", command); network->SendReply(session.ip, 400, "Unknown rr_ command"); return false; } if (response != nullptr) { network->SendReply(session.ip, 200 | rcJson, response); } else if (!processingDeferredRequest) { network->SendReply(session.ip, 500, "No buffer available"); } return processingDeferredRequest; }