Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filament backup, new and modified events for printer inventory plugins. #71

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions octoprint_Spoolman/SpoolmanPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def triggerPluginEvent(self, eventType, eventPayload = {}):

def on_after_startup(self):
self._logger.info("[Spoolman][init] Plugin activated")
self.loadSaveSpoolUsage()

# Printing events handlers
def on_event(self, event, payload):
Expand All @@ -68,9 +69,13 @@ def on_event(self, event, payload):
event == Events.PRINT_CANCELLED
):
self.handlePrintingStatusChange(event)
self.resetSaveStatus(event)

pass

if event == Events.FILE_SELECTED:
self.handleFileSelected(payload)

def on_sentGCodeHook(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
if not self._isInitialized:
return
Expand Down Expand Up @@ -129,6 +134,8 @@ def get_settings_defaults(self):
SettingsKeys.SHOW_LOT_NUMBER_COLUMN_IN_SPOOL_SELECT_MODAL: False,
SettingsKeys.SHOW_LOT_NUMBER_IN_SIDE_BAR: False,
SettingsKeys.SHOW_SPOOL_ID_IN_SIDE_BAR: False,
SettingsKeys.STATUS_BACKUP: False,
SettingsKeys.BACKUP_DATA: None
}

return settings
Expand All @@ -144,6 +151,9 @@ def register_custom_events(*args, **kwargs):
PluginEvents.SPOOL_SELECTED,
PluginEvents.SPOOL_USAGE_COMMITTED,
PluginEvents.SPOOL_USAGE_ERROR,
PluginEvents.SPOOL_INFO_ERROR,
PluginEvents.SPOOL_USAGE_COMMITTED_RECOVERY,
PluginEvents.SPOOL_FILE_SELECTED,
]

def get_update_information(self):
Expand Down
3 changes: 3 additions & 0 deletions octoprint_Spoolman/common/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ class PluginEvents():
SPOOL_SELECTED = "spool_selected"
SPOOL_USAGE_COMMITTED = "spool_usage_committed"
SPOOL_USAGE_ERROR = "spool_usage_error"
SPOOL_INFO_ERROR = "spool_info_error"
SPOOL_USAGE_COMMITTED_RECOVERY = "spool_usage_committed_recovery"
SPOOL_FILE_SELECTED = "spool_file_selected"
2 changes: 2 additions & 0 deletions octoprint_Spoolman/common/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ class SettingsKeys():
SHOW_LOT_NUMBER_COLUMN_IN_SPOOL_SELECT_MODAL = "showLotNumberColumnInSpoolSelectModal"
SHOW_LOT_NUMBER_IN_SIDE_BAR = "showLotNumberInSidebar"
SHOW_SPOOL_ID_IN_SIDE_BAR = "showSpoolIdInSidebar"
BACKUP_DATA = "backupData"
STATUS_BACKUP = "isstatusBackupEnabled"
2 changes: 2 additions & 0 deletions octoprint_Spoolman/modules/PluginAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def handleUpdateActiveSpool(self):
}
)

self.infoSpool(spoolId,toolId)

return flask.jsonify({
"data": {}
})
Expand Down
247 changes: 244 additions & 3 deletions octoprint_Spoolman/modules/PrinterHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ def initialize(self):
self.lastPrintCancelled = False
self.lastPrintOdometer = None
self.lastPrintOdometerLoad = None

self.currentZ = None
self.resetExtruder = False
self.dataSpool = {}

def handlePrintingStatusChange(self, eventType):
if eventType == Events.PRINT_STARTED:
self.lastPrintCancelled = False
self.lastPrintOdometer = gcode()
self.lastPrintOdometerLoad = self.lastPrintOdometer._load(None)
self.resetExtruder = False

next(self.lastPrintOdometerLoad)

Expand Down Expand Up @@ -59,6 +63,8 @@ def commitSpoolUsage(self):
current_extrusion_stats = copy.deepcopy(peek_stats_helpers['get_current_extrusion_stats']())

peek_stats_helpers['reset_extrusion_stats']()

self.resetExtruder = True

selectedSpoolIds = self._settings.get([SettingsKeys.SELECTED_SPOOL_IDS])

Expand All @@ -78,11 +84,25 @@ def commitSpoolUsage(self):

selectedSpoolId = selectedSpool['spoolId']

weight = self.getWeight(str(toolIdx),toolExtrusionLength)
cost = self.getCost(str(toolIdx),weight)

filament = self.dataSpool.get(str(toolIdx), {}).get('filament', {})

name = filament.get('name', None)
material = filament.get('material', None)
colorHex = filament.get('color_hex', None)

self._logger.info(
"Extruder '%s', spool id: %s, usage: %s",
"Extruder '%s', spool id: %s, usage length: %s, weight: %s, cost: %s name: %s material: %s colorHex: %s",
toolIdx,
selectedSpoolId,
toolExtrusionLength
toolExtrusionLength,
weight,
cost,
name,
material,
colorHex
)

result = self.getSpoolmanConnector().handleCommitSpoolUsage(selectedSpoolId, toolExtrusionLength)
Expand All @@ -101,5 +121,226 @@ def commitSpoolUsage(self):
'toolIdx': toolIdx,
'spoolId': selectedSpoolId,
'extrusionLength': toolExtrusionLength,
'weight': weight,
'cost': cost,
'name': name,
'material': material,
'colorHex': colorHex
}
)

def saveFilamentStatusChange(self):
if not self._printer.is_printing() or not self._settings.get_boolean([SettingsKeys.STATUS_BACKUP]):
return

backup_save = self._settings.get([SettingsKeys.BACKUP_DATA])

if backup_save is None:
backup_save = {}

peek_stats_helpers = self.lastPrintOdometerLoad.send(False)
current_extrusion_stats = copy.deepcopy(peek_stats_helpers['get_current_extrusion_stats']())

selectedSpoolIds = self._settings.get([SettingsKeys.SELECTED_SPOOL_IDS])

for toolIdx, toolExtrusionLength in enumerate(current_extrusion_stats['extrusionAmount']):
try:
toolIdxStr = str(toolIdx)
selectedSpool = selectedSpoolIds.get(toolIdxStr, None)

except Exception as e:
self._logger.info("Extruder Saved '%s', spool id: none. Error: %s", toolIdxStr, e)
continue

if not selectedSpool or selectedSpool.get('spoolId', None) is None:
continue

selectedSpoolId = selectedSpool['spoolId']

weight = self.getWeight(toolIdxStr, toolExtrusionLength)
cost = self.getCost(toolIdxStr, weight)

filament = self.dataSpool.get(toolIdxStr, {}).get('filament', {})

name = filament.get('name', None)
material = filament.get('material', None)
colorHex = filament.get('color_hex', None)

if toolIdxStr not in backup_save:
backup_save[toolIdxStr] = {}

if self.resetExtruder == True:
backup_save[toolIdxStr]['spoolId'] = selectedSpoolId
backup_save[toolIdxStr]['name'] = name
backup_save[toolIdxStr]['material'] = material
backup_save[toolIdxStr]['color_hex'] = colorHex

backup_save[toolIdxStr]['totalExtrusionLength'] = toolExtrusionLength + backup_save[toolIdxStr].get('sessionExtrusionLength', 0) + backup_save[toolIdxStr].get('totalExtrusionLength', 0)
backup_save[toolIdxStr]['sessionExtrusionLength'] = 0

backup_save[toolIdxStr]['totalWeight'] = weight + backup_save[toolIdxStr].get('sessionWeight', 0) + backup_save[toolIdxStr].get('totalWeight', 0)
backup_save[toolIdxStr]['sessionWeight'] = 0

backup_save[toolIdxStr]['totalCost'] = cost + backup_save[toolIdxStr].get('sessionCost', 0) + backup_save[toolIdxStr].get('totalCost', 0)
backup_save[toolIdxStr]['sessionCost'] = 0

if self.resetExtruder == False:
backup_save[toolIdxStr]['spoolId'] = selectedSpoolId
backup_save[toolIdxStr]['name'] = name
backup_save[toolIdxStr]['material'] = material
backup_save[toolIdxStr]['color_hex'] = colorHex
backup_save[toolIdxStr]['sessionExtrusionLength'] = toolExtrusionLength
backup_save[toolIdxStr]['sessionWeight'] = weight
backup_save[toolIdxStr]['sessionCost'] = cost

self.resetExtruder = False

self._settings.set([SettingsKeys.BACKUP_DATA], backup_save)
self._settings.save()

def loadSaveSpoolUsage(self):
backup_data = self._settings.get([SettingsKeys.BACKUP_DATA])

if not isinstance(backup_data, dict):
return

for toolIdx, spool_data in backup_data.items():
toolIdxStr = str(toolIdx)
selectedSpoolId = spool_data.get('spoolId')
name = spool_data.get('name')
material = spool_data.get('material')
colorHex = spool_data.get('color_hex')

toolExtrusionLength = spool_data.get('totalExtrusionLength', 0) + spool_data.get('sessionExtrusionLength', 0)
weight = spool_data.get('totalWeight', 0) + spool_data.get('sessionWeight', 0)
cost = spool_data.get('totalCost', 0) + spool_data.get('sessionCost', 0)

if not selectedSpoolId or toolExtrusionLength is None:
self._logger.info("Loading saved spool usage: Extruder '%s', spool id: none or invalid data", toolIdxStr)
continue

result = self.getSpoolmanConnector().handleCommitSpoolUsage(selectedSpoolId, toolExtrusionLength)

if result.get('error', None):
self.triggerPluginEvent(
Events.PLUGIN_SPOOLMAN_SPOOL_USAGE_ERROR,
result['error']
)
return

self.triggerPluginEvent(
Events.PLUGIN_SPOOLMAN_SPOOL_USAGE_COMMITTED_RECOVERY,
{
'toolIdx': toolIdxStr,
'spoolId': selectedSpoolId,
'extrusionLength': toolExtrusionLength,
'weight': weight,
'cost': cost,
'name': name,
'material': material,
'colorHex': colorHex
}
)


self._settings.set([SettingsKeys.BACKUP_DATA], None)
self._settings.save()

def resetSaveStatus(self,eventType):
if (
eventType == Events.PRINT_DONE or
eventType == Events.PRINT_CANCELLED and
self._settings.get_boolean([SettingsKeys.STATUS_BACKUP])
):
self._settings.set([SettingsKeys.BACKUP_DATA], None)
self._settings.save()

def infoSpool(self,SpoolId,toolId):
if not hasattr(self, 'dataSpool') or self.dataSpool is None:
self.dataSpool = {}

if SpoolId is None:
return

result = self.getSpoolmanConnector().handleCommitSpoolInfo(SpoolId)

if result.get('error', None):
self.triggerPluginEvent(
Events.PLUGIN_SPOOLMAN_SPOOL_INFO_ERROR,
result['error']
)
return

self.dataSpool[toolId] = result

def handleFileSelected(self, payload):
self._logger.info("File selected" + str(payload))
selectedSpoolIds = self._settings.get([SettingsKeys.SELECTED_SPOOL_IDS])
jobFilamentUsage = self.getCurrentJobFilamentUsage()

for toolIdx, spoolData in selectedSpoolIds.items():
selectedSpoolId = spoolData.get('spoolId', None)

if selectedSpoolId:
self.infoSpool(selectedSpoolId, toolIdx)

else:
self._logger.warning("No spoolId found for extruder %s", toolIdx)

if jobFilamentUsage['jobHasFilamentLengthData']:
for toolIndex, filamentLength in enumerate(jobFilamentUsage["jobFilamentLengthsPerTool"]):

weight = self.getWeight(str(toolIndex), filamentLength)
cost = self.getCost(str(toolIndex), weight)

filament = self.dataSpool.get(str(toolIndex), {}).get('filament', {})

name = filament.get('name', None)
material = filament.get('material', None)
colorHex = filament.get('color_hex', None)

self.triggerPluginEvent(
Events.PLUGIN_SPOOLMAN_SPOOL_FILE_SELECTED,
{
'toolIdx': str(toolIndex),
'spoolId': self.dataSpool.get(str(toolIndex), {}).get('id', None),
'estimatedExtrusionLength': filamentLength,
'estimatedWeight': weight,
'estimatedCost': cost,
'name': name,
'material': material,
'colorHex': colorHex
}
)

def getWeight(self,toolIdx,toolExtrusionLength):
if toolIdx not in self.dataSpool:
return 0

spool_data = self.dataSpool[toolIdx]['filament']

density = spool_data.get('density', None)
diameter = spool_data.get('diameter', None)

if density is None or diameter is None:
return 0

weight = self.getFilamentWeight(toolExtrusionLength, density, diameter)

return weight

def getCost(self,toolIdx,weight):
if toolIdx not in self.dataSpool:
return 0

spool_data = self.dataSpool[toolIdx]

initial_weight = spool_data.get('initial_weight', 0)
price = spool_data.get('price', 0)

if initial_weight <= 0 or price <= 0:
return 0

cost_use = (weight / initial_weight) * price

return cost_use
47 changes: 47 additions & 0 deletions octoprint_Spoolman/modules/SpoolmanConnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,50 @@ def handleCommitSpoolUsage(self, spoolId, spoolUsedLength):
return {
"data": {}
}

def handleCommitSpoolInfo(self, spoolId):
precheckResult = self._precheckSpoolman()

if precheckResult and precheckResult.get('error', False):
return precheckResult

spoolIdStr = str(spoolId)
endpointUrl = self._createSpoolmanEndpointUrl("/spool/" + spoolIdStr)

self._logSpoolmanCall(endpointUrl)

try:
session = requests.Session()
session.verify = self.verifyConfig
retries = Retry(total = 3, backoff_factor = 1, status_forcelist = [ 500, 502, 503, 504 ])

session.mount(self.instanceUrl, HTTPAdapter(max_retries=retries))

response = session.get(
url = endpointUrl,
json = {},
timeout = 1
)
except Exception as caughtException:
return self._handleSpoolmanConnectionError(caughtException)

if response.status_code == 404:
return self._handleSpoolmanError(
response,
{
"code": "spoolman_api__spool_not_found",
"spoolman_api": {
"status_code": response.status_code,
},
"data": {
"spoolId": spoolIdStr,
},
}
)

if response.status_code != 200:
return self._handleSpoolmanError(response)

self._logSpoolmanSuccess(response)

return response.json()
Loading