2013-12-20 16:29:46 +01:00
--
-- This file is part of the Doodle3D project (http://doodle3d.com).
--
-- @copyright 2013, Doodle3D
-- @license This software is licensed under the terms of the GNU GPL v2 or later.
-- See file LICENSE.txt or visit http://www.gnu.org/licenses/gpl.html for full license details.
2013-11-04 22:34:09 +01:00
---
-- This file contains all valid configuration keys, their default values and optional constraints.
-- The table names are used as configuration key names, where underscores ('`_`') may be used to denote semi-categories.
-- The settings interface replaces periods ('`.`') by underscores so for instance `network.ap.address` will
-- be translated to `network_ap_address`. Multi-word names should be notated as camelCase.
--
-- Valid fields for the tables are:
--
-- - _default_: the default value (used when the key is not set in UCI config)
-- - _type_: used for basic type checking, one of bool, int, float or string
-- - _description_: A descriptive text usable by API clients
-- - _min_, _max_, _regex_: optional constraints (min and max constrain value for numbers, or length for strings)
-- - _isValid_: an optional function which should return true for valid values and false for invalid ones
2014-02-24 15:22:08 +01:00
-- - _subSection: optional: setting name of which current value is used as the uci section where this setting should be loaded from. Otherwise it's retrieved from the generic section. Setting subsection also means it will first try to get a default from subconf_defaults, if that doesn't exsist it will use the regular default
2013-11-04 22:34:09 +01:00
-- The configuration keys themselves document themselves rather well, hence they are not included in the generated documentation.
--
-- NOTE: the all-caps definitions should be changed into configuration keys, or moved to a better location.
2013-08-29 01:40:51 +02:00
local printer = require ( ' util.printer ' )
2013-10-28 00:58:14 +01:00
local log = require ( ' util.logger ' )
local utils = require ( ' util.utils ' )
2013-07-17 08:25:24 +02:00
2013-07-26 02:17:05 +02:00
local M = { }
2013-07-17 17:43:33 +02:00
2013-11-04 22:34:09 +01:00
--- This constant should only be true during development. It replaces `pcall` by regular `call`.
-- Pcall protects the script from invocation exceptions, which is what we need except during debugging.
-- When this flag is true, normal calls will be used so we can inspect stack traces.
2013-07-26 10:18:55 +02:00
M.DEBUG_PCALLS = false
2013-07-17 08:25:24 +02:00
2013-11-04 22:34:09 +01:00
--- This constant enables debugging of the REST API from the command-line by emulating GET/POST requests.
-- Specify the path and optionally the request method as follows: `d3dapi p=/mod/func r=POST`.
2016-02-05 15:16:36 +01:00
-- Note that the command-line script redirects the output streams to '/tmp/wifibox.cgi-fallback.log'
-- meaning that any stack traces can be found there.
2013-07-24 18:49:07 +02:00
M.DEBUG_API = true
2013-11-04 22:34:09 +01:00
--- If enabled, REST responses will contain 'module' and 'function' keys describing what was requested.
2013-07-17 08:25:24 +02:00
M.API_INCLUDE_ENDPOINT_INFO = false
2013-11-04 22:34:09 +01:00
--- This base path is used in @{rest.response}. It includes any base path if necessary (e.g. 'localhost/~user').
M.API_BASE_URL_PATH = ' doodle3d.com '
2013-07-17 08:25:24 +02:00
2016-01-05 18:25:38 +01:00
M.system_log_level = {
default = ' info ' ,
type = ' string ' ,
description = ' Log level for firmware and print server ' ,
isValid = function ( value )
-- Note: level names mimic those in print3d
levelNames = { ' quiet ' , ' error ' , ' warning ' , ' info ' , ' verbose ' , ' bulk ' }
for i , v in ipairs ( levelNames ) do
if value == v then
return true
end
end
return false , ' not one of ' .. table.concat ( levelNames , ' , ' )
end
}
2013-07-26 02:17:05 +02:00
M.network_ap_ssid = {
2013-09-18 21:16:54 +02:00
default = ' Doodle3D-%%MAC_ADDR_TAIL%% ' ,
2013-07-17 17:43:33 +02:00
type = ' string ' ,
2013-09-18 21:16:54 +02:00
description = ' Access Point mode SSID (name) ' ,
2013-07-17 17:43:33 +02:00
min = 1 ,
max = 32
}
2013-07-26 02:17:05 +02:00
M.network_ap_address = {
2013-07-17 17:43:33 +02:00
default = ' 192.168.10.1 ' ,
type = ' string ' ,
description = ' Access Point mode IP address ' ,
regex = ' %d+ \ .%d+ \ .%d+ \ .%d+ '
}
2013-10-14 14:01:22 +02:00
M.network_ap_key = {
2013-10-17 15:28:57 +02:00
default = ' ' ,
type = ' string ' ,
description = ' Access Point security key ' ,
isValid = function ( value )
2013-11-04 22:34:09 +01:00
if value == " " then
2013-10-17 15:28:57 +02:00
return true ;
elseif value : len ( ) < 8 then
2013-10-18 16:16:05 +02:00
return false , " too short "
2013-10-17 15:28:57 +02:00
elseif value : len ( ) > 63 then
2013-10-18 16:16:05 +02:00
return false , " too long "
2013-10-17 15:28:57 +02:00
else
return true
end
end
2013-10-14 14:01:22 +02:00
}
2013-07-26 02:17:05 +02:00
M.network_ap_netmask = {
2013-07-17 17:43:33 +02:00
default = ' 255.255.255.0 ' ,
type = ' string ' ,
description = ' Access Point mode netmask ' ,
regex = ' %d+ \ .%d+ \ .%d+ \ .%d+ '
}
2013-07-17 08:25:24 +02:00
2013-09-27 18:38:31 +02:00
M.network_cl_wifiboxid = {
default = ' Doodle3D-%%MAC_ADDR_TAIL%% ' ,
type = ' string ' ,
description = ' Client mode WiFi box id ' ,
min = 1 ,
max = 32
}
2013-08-28 14:29:13 +02:00
M.printer_type = {
default = ' ultimaker ' ,
type = ' string ' ,
description = ' ' ,
2013-09-27 18:25:16 +02:00
isValid = function ( value )
2013-08-29 01:40:51 +02:00
local printers = printer.supportedPrinters ( )
2013-09-27 18:25:16 +02:00
return printers [ value ] ~= nil
2013-08-29 01:40:51 +02:00
end
2013-08-28 14:29:13 +02:00
}
2013-11-28 17:48:00 +01:00
2013-12-05 17:31:32 +01:00
M.printer_dimensions_x = {
default = 200 ,
2013-12-19 14:37:11 +01:00
default_delta_rostockmax = 0 ,
2014-10-10 11:04:25 +02:00
default__3Dison_plus = 227 ,
2013-12-19 14:37:11 +01:00
default_deltamaker = 0 ,
default_kossel = 0 ,
2014-02-24 15:22:08 +01:00
default_minifactory = 150 ,
2015-05-27 17:53:53 +02:00
default_lulzbot_taz_4 = 298 ,
2015-06-08 15:34:07 +02:00
default_ultimaker2go = 120 ,
2015-06-09 16:12:56 +02:00
default_doodle_dream = 120 ,
2015-06-10 17:48:09 +02:00
default_wanhao_duplicator4 = 210 ,
2015-08-21 13:59:38 +02:00
default_colido_2_0_plus = 230 ,
default_colido_x3045 = 300 ,
default_colido_compact = 130 ,
2013-11-28 17:48:00 +01:00
subSection = ' printer_type ' ,
2013-10-26 03:20:26 +02:00
type = ' int ' ,
description = ' ' ,
min = 0
}
2013-12-05 17:31:32 +01:00
M.printer_dimensions_y = {
default = 200 ,
2013-12-19 14:37:11 +01:00
default_delta_rostockmax = 0 ,
2014-10-10 11:04:25 +02:00
default__3Dison_plus = 148 ,
2013-12-19 14:37:11 +01:00
default_deltamaker = 0 ,
default_kossel = 0 ,
2014-01-30 13:21:21 +01:00
default_minifactory = 150 ,
2015-05-27 17:53:53 +02:00
default_lulzbot_taz_4 = 275 ,
2015-06-08 15:34:07 +02:00
default_ultimaker2go = 120 ,
2015-06-09 16:12:56 +02:00
default_doodle_dream = 120 ,
2015-06-10 17:48:09 +02:00
default_wanhao_duplicator4 = 140 ,
2015-08-21 13:59:38 +02:00
default_colido_2_0_plus = 150 ,
default_colido_x3045 = 300 ,
default_colido_compact = 130 ,
2013-12-05 17:31:32 +01:00
subSection = ' printer_type ' ,
type = ' int ' ,
description = ' ' ,
min = 0
}
M.printer_dimensions_z = {
default = 200 ,
2014-01-30 13:21:21 +01:00
default_minifactory = 155 ,
2014-10-10 11:04:25 +02:00
default__3Dison_plus = 150 ,
2015-05-27 17:53:53 +02:00
default_lulzbot_taz_4 = 250 ,
2015-06-08 16:11:44 +02:00
default_ultimaker2go = 112 ,
2015-06-09 16:12:56 +02:00
default_doodle_dream = 80 ,
2015-06-10 17:48:09 +02:00
default_wanhao_duplicator4 = 140 ,
2015-08-21 13:59:38 +02:00
default_colido_2_0_plus = 140 ,
default_colido_x3045 = 450 ,
default_colido_compact = 115 ,
default_colido_diy = 170 ,
2013-11-29 11:03:27 +01:00
subSection = ' printer_type ' ,
2013-10-26 03:20:26 +02:00
type = ' int ' ,
description = ' ' ,
min = 0
}
2013-12-18 17:45:53 +01:00
M.printer_heatedbed = {
default = false ,
default_ultimaker2 = true ,
default_makerbot_replicator2x = true ,
2014-01-30 13:21:21 +01:00
default_minifactory = true ,
2015-05-27 17:53:53 +02:00
default_lulzbot_taz_4 = true ,
2015-06-10 17:48:09 +02:00
default_wanhao_duplicator4 = true ,
2015-08-21 13:59:38 +02:00
default_colido_2_0_plus = true ,
default_colido_m2020 = true ,
default_colido_x3045 = true ,
2013-12-18 17:45:53 +01:00
subSection = ' printer_type ' ,
type = ' bool ' ,
description = ' Printer has heated bed ' ,
}
2015-06-09 17:11:02 +02:00
M.printer_filamentThickness = {
default = 2.89 ,
default_doodle_dream = 1.75 ,
2015-06-10 17:48:09 +02:00
default_wanhao_duplicator4 = 1.75 ,
2015-06-09 17:11:02 +02:00
type = ' float ' ,
description = ' ' ,
min = 0.0 ,
subSection = ' printer_type '
}
2013-08-28 14:29:13 +02:00
2015-06-15 14:03:28 +02:00
local default_makerbot_startcode = ' ;@printer {printerType} \n M136 (enable build) \n M73 P0 (Set build percentage to 0) \n G162 X Y F2000 (home XY axes to maximum) \n G161 Z F900 (home Z axis to minimum quick) \n G92 X0 Y0 Z0 A0 B0 (set axis positions to 0) \n G1 Z5.0 F900 (move Z axis down) \n G161 Z F100 (home Z axis to minimum more precise) \n G92 X0 Y0 Z0 A0 B0 (set axis positions to 0) \n G1 Z2.0 F900 (move Z axis to safety height) \n G1 X-264 Y-145 Z0 F3300.0 (move XY to start position) \n G92 X0 Y0 Z0 A0 B0 (set axis position to 0) \n G130 X20 Y20 A20 B20 (Lower stepper Vrefs while heating) \n {if heatedBed}M140 S{printingBedTemp} T0 (Set bed temp) \n M135 T0 (use first extruder) \n M104 S{printingTemp} T0 (set extruder temp) \n M133 T0 (Wait for extruder) \n G130 X127 Y127 A127 B127 (Set Stepper motor Vref to defaults) \n G1 F100 A10 (extrude 10mm) \n G92 A0 (reset extruder) \n G0 Z20 (move up, to lose filament) '
2013-12-19 14:37:11 +01:00
local default_deltabot_startcode = ' ;Generated with Doodle3D (deltabot) \n M109 S{printingTemp} ;set target temperature \n {if heatedBed}M190 S{printingBedTemp} ;set target bed temperature \n G21 ;metric values \n G91 ;relative positioning \n M107 ;start with the fan off \n G28 ; move to home \n G92 E0 ;zero the extruded length \n G90 ;absolute positioning \n M117 Printing Doodle... ;display message (20 characters to clear whole screen) '
2015-06-15 14:03:28 +02:00
local default_ultimaker2_startcode = ' ;Generated with Doodle3D (ultimaker2) \n M109 S{printingTemp} ;set target temperature \n {if heatedBed}M190 S{printingBedTemp} ;set target bed temperature \n G21 ;metric values \n G90 ;absolute positioning \n M107 ;start with the fan off \n G28 ; home to endstops \n G1 Z15 F9000 ;move the platform down 15mm \n G92 E0 ;zero the extruded length \n G1 F200 E10 ;extrude 10mm of feed stock \n G92 E0 ;zero the extruded length again \n G1 F9000 \n M117 Printing Doodle... ;display message (20 characters to clear whole screen) \n '
2013-11-28 17:48:00 +01:00
M.printer_startcode = {
2013-12-18 17:45:53 +01:00
default = ' ;Generated with Doodle3D (default) \n M109 S{printingTemp} ;set target temperature \n {if heatedBed}M190 S{printingBedTemp} ;set target bed temperature \n G21 ;metric values \n G91 ;relative positioning \n M107 ;start with the fan off \n G28 X0 Y0 ;move X/Y to min endstops \n G28 Z0 ;move Z to min endstops \n G1 Z15 F9000 ;move the platform down 15mm \n G92 E0 ;zero the extruded length \n G1 F200 E10 ;extrude 10mm of feed stock \n G92 E0 ;zero the extruded length again \n G92 E0 ;zero the extruded length again \n G1 F9000 \n G90 ;absolute positioning \n M117 Printing Doodle... ;display message (20 characters to clear whole screen) ' ,
2015-06-15 14:03:28 +02:00
default_ultimaker2 = default_ultimaker2_startcode ,
default_ultimaker2go = default_ultimaker2_startcode ,
2014-10-09 17:39:17 +02:00
default__3Dison_plus = ' ;@printer {printerType} \n M136 (enable build) \n M73 P0 \n G21 \n G90 \n M103 \n ;M109 S50 T0 \n M140 S50 T0 \n M104 S{printingTemp} T0 \n ;M134 T0 \n M135 T0 \n M104 S{printingTemp} T0 \n G162 X Y F2000(home XY axes maximum) \n G161 Z F900(home Z axis minimum) \n G92 X113.5 Y74 Z-5 A0 B0 (set Z to -5) \n G1 Z0.0 F900(move Z to 0) \n G161 Z F100(home Z axis minimum) \n M132 X Y Z A B (Recall stored home offsets for XYZAB axis) \n G1 Z50 F3300 \n G130 X20 Y20 A20 B20 (Lower stepper Vrefs while heating) \n M133 T0 \n M6 T0 \n G130 X127 Y127 A127 B127 (Set Stepper motor Vref to defaults) \n G0 Z0.2 F3000 \n G1 Z0 F100 A10 ;extrude 10mm \n G92 X227 Y148 Z0 A0 ;reset again \n G1 X227 Y148 Z0 ' ,
2013-11-29 11:03:27 +01:00
default_makerbot_generic = default_makerbot_startcode ,
default_makerbot_replicator2 = default_makerbot_startcode ,
2013-12-05 16:49:34 +01:00
default_makerbot_replicator2x = default_makerbot_startcode ,
2013-11-29 11:03:27 +01:00
default_makerbot_thingomatic = default_makerbot_startcode ,
2015-06-10 17:48:09 +02:00
default_wanhao_duplicator4 = default_makerbot_startcode ,
2013-12-19 14:37:11 +01:00
default_delta_rostockmax = default_deltabot_startcode ,
default_deltamaker = default_deltabot_startcode ,
default_kossel = default_deltabot_startcode ,
2013-11-28 17:48:00 +01:00
type = ' string ' ,
subSection = ' printer_type ' ,
description = ' '
}
2015-06-10 17:50:05 +02:00
local default_makerbot_endcode = ' M73 P100 \n G92 A0 B0 ;reset extruder position to prevent retraction \n M18 A B(Turn off A and B Steppers) \n G162 Z F900 \n G162 X Y F2000 \n M18 X Y Z(Turn off steppers after a build) \n {if heatedBed}M140 S{preheatBedTemp} T0 \n M104 S{preheatTemp} T0 \n M73 P100 (end build progress ) \n M72 P1 ( Play Ta-Da song ) \n M137 (build end notification) '
2013-12-19 14:37:11 +01:00
local default_deltabot_endcode = ' M107 ;fan offG91 ;relative positioningG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressureG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even moreG28 ;move to homeM84 ;disable axes / steppersG90 ;absolute positioningM109 S0 ; hot end off{if heatedBed}M140 S{preheatBedTemp}M117 Done ;display message (20 characters to clear whole screen) '
2015-06-15 14:03:28 +02:00
local default_ultimaker2_endcode = ' M107 ;fan off \n G91 ;relative positioning \n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure \n G1 Z+5.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more \n G28 ;home the printer \n M84 ;disable axes / steppers \n G90 ;absolute positioning \n M104 S{preheatTemp} \n {if heatedBed}M140 S{preheatBedTemp} \n M117 Done ;display message (20 characters to clear whole screen) '
2013-11-28 17:48:00 +01:00
M.printer_endcode = {
2013-12-18 17:45:53 +01:00
default = ' M107 ;fan off \n G91 ;relative positioning \n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure \n G1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more \n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way \n M84 ;disable axes / steppers \n G90 ;absolute positioning \n M104 S{preheatTemp} \n {if heatedBed}M140 S{preheatBedTemp} \n M117 Done ;display message (20 characters to clear whole screen) ' ,
2015-06-15 14:03:28 +02:00
default_ultimaker2 = default_ultimaker2_endcode ,
default_ultimaker2go = default_ultimaker2_endcode ,
2014-10-09 16:24:55 +02:00
default__3Dison_plus = ' M73 P100 \n G92 A0 B0 ;reset extruder position to prevent retraction \n M18 A B(Turn off A and B Steppers) \n G1 Z155 F900 \n G162 X Y F2000 \n M18 X Y Z(Turn off steppers after a build) \n M140 S35 T0 \n M104 S180 T0 \n M73 P100 (end build progress ) \n M72 P1 ( Play Ta-Da song ) \n M137 (build end notification) \n ' ,
2013-11-29 11:03:27 +01:00
default_makerbot_generic = default_makerbot_endcode ,
default_makerbot_replicator2 = default_makerbot_endcode ,
2013-12-05 16:49:34 +01:00
default_makerbot_replicator2x = default_makerbot_endcode ,
2013-11-29 11:03:27 +01:00
default_makerbot_thingomatic = default_makerbot_endcode ,
2015-06-10 17:48:09 +02:00
default_wanhao_duplicator4 = default_makerbot_endcode ,
2013-12-19 14:37:11 +01:00
default_delta_rostockmax = default_deltabot_endcode ,
default_deltamaker = default_deltabot_endcode ,
default_kossel = default_deltabot_endcode ,
2013-11-28 17:48:00 +01:00
type = ' string ' ,
subSection = ' printer_type ' ,
description = ' '
}
2013-08-28 14:29:13 +02:00
M.printer_baudrate = {
default = ' 115200 ' ,
2013-07-17 08:25:24 +02:00
type = ' int ' ,
2013-08-28 14:29:13 +02:00
description = ' ' ,
2013-09-27 18:25:16 +02:00
isValid = function ( value )
2013-08-29 01:40:51 +02:00
local baudrates = printer.supportedBaudRates ( )
2013-09-27 18:25:16 +02:00
return baudrates [ tostring ( value ) ] ~= nil
2013-08-29 01:40:51 +02:00
end
2013-07-26 02:17:05 +02:00
}
2013-08-28 14:29:13 +02:00
M.printer_temperature = {
default = 230 ,
2013-07-26 02:17:05 +02:00
type = ' int ' ,
2013-10-22 16:00:37 +02:00
description = ' printing temperature ' ,
min = 0
}
M.printer_bed_temperature = {
default = 70 ,
type = ' int ' ,
description = ' printing bed temperature ' ,
2013-07-26 02:17:05 +02:00
min = 0
}
M.printer_layerHeight = {
default = 0.2 ,
type = ' float ' ,
description = ' ' ,
min = 0.0
}
M.printer_wallThickness = {
default = 0.5 ,
type = ' float ' ,
description = ' ' ,
min = 0.0
}
M.printer_speed = {
default = 70 ,
type = ' int ' ,
description = ' ' ,
min = 0
}
M.printer_travelSpeed = {
default = 200 ,
type = ' int ' ,
description = ' ' ,
min = 0
}
M.printer_useSubLayers = {
default = true ,
type = ' bool ' ,
description = ' Continuously move platform while printing instead of once per layer '
}
M.printer_firstLayerSlow = {
default = true ,
2013-07-28 05:01:58 +02:00
type = ' bool ' ,
2013-08-04 11:26:47 +02:00
description = ' Print the first layer slowly to get a more stable start '
2013-07-26 02:17:05 +02:00
}
2014-01-10 17:31:32 +01:00
M.printer_bottomLayerSpeed = {
default = 35 ,
type = ' int ' ,
description = ' If first layers are to be printed slowly, use this speed '
}
M.printer_bottomFlowRate = {
default = 2 ,
type = ' float ' ,
description = ' Multiplication factor for filament flow rate in first few layers '
}
2016-01-06 19:26:31 +01:00
M.printer_bottomEnableTraveling = {
default = true ,
type = ' bool ' ,
description = ' Enable traveling on bottom layers, disabling this might improve adhesion on some printers '
}
2013-09-27 18:25:16 +02:00
M.printer_heatup_enabled = {
default = true ,
type = ' bool ' ,
description = ' '
}
M.printer_heatup_temperature = {
2013-09-25 12:06:28 +02:00
default = 180 ,
type = ' int ' ,
description = ' '
2013-07-26 02:17:05 +02:00
}
2013-10-22 16:00:37 +02:00
M.printer_heatup_bed_temperature = {
default = 70 ,
type = ' int ' ,
description = ' '
}
2013-08-02 14:00:23 +02:00
M.printer_retraction_enabled = {
default = true ,
type = ' bool ' ,
description = ' '
}
2013-07-26 02:17:05 +02:00
M.printer_retraction_speed = {
default = 50 ,
type = ' int ' ,
description = ' ' ,
min = 0
}
M.printer_retraction_minDistance = {
default = 5 ,
type = ' int ' ,
description = ' ' ,
min = 0
}
M.printer_retraction_amount = {
default = 3 ,
2014-01-27 13:39:30 +01:00
type = ' float ' ,
2013-07-26 02:17:05 +02:00
description = ' ' ,
min = 0
}
2013-09-18 12:13:07 +02:00
M.printer_enableTraveling = {
2013-12-04 16:16:21 +01:00
default = true ,
2013-09-18 12:13:07 +02:00
type = ' bool ' ,
description = ' '
}
2013-12-05 17:31:32 +01:00
-- M.printer_maxObjectHeight = {
-- default = 150,
-- type = 'int',
-- description = 'Maximum height that will be printed',
-- min = 0
-- }
2013-08-28 14:29:13 +02:00
2013-09-18 12:13:07 +02:00
M.printer_screenToMillimeterScale = {
default = 0.3 ,
type = ' float ' ,
description = ' ' ,
}
2013-08-28 14:29:13 +02:00
M.doodle3d_simplify_minDistance = {
default = 3 ,
type = ' int ' ,
description = ' ' ,
min = 0
}
2013-12-19 16:34:59 +01:00
M.doodle3d_tour_enabled = {
default = true ,
type = ' bool ' ,
description = ' Show tour to new users '
}
2014-02-25 14:26:31 +01:00
M.doodle3d_update_includeBetas = {
2014-02-21 09:54:03 +01:00
default = false ,
type = ' bool ' ,
2014-02-25 14:26:31 +01:00
description = ' Include beta releases when updating '
}
M.doodle3d_update_baseUrl = {
default = ' http://doodle3d.com/updates ' ,
type = ' string ' ,
description = ' '
2014-02-21 09:54:03 +01:00
}
2013-07-17 08:25:24 +02:00
return M