rearrange the code

This commit is contained in:
George Fu
2020-04-21 14:37:59 +08:00
parent 6f24fb351e
commit 0cea8d6ba9
11 changed files with 449 additions and 354 deletions

View File

@@ -566,3 +566,4 @@ void ESPWebDAV::handleDelete(ResourceType resource) {
send("200 OK", NULL, "");
}
ESPWebDAV dav;

View File

@@ -4,11 +4,9 @@
#define DEBUG
#ifdef DEBUG
#define DBG_INIT(...) { Serial.begin(__VA_ARGS__); }
#define DBG_PRINT(...) { Serial.print(__VA_ARGS__); }
#define DBG_PRINTLN(...) { Serial.println(__VA_ARGS__); }
#else
#define DBG_INIT(...) {}
#define DBG_PRINT(...) {}
#define DBG_PRINTLN(...) {}
#endif
@@ -83,7 +81,4 @@ protected:
int _contentLength;
};
extern ESPWebDAV dav;

View File

@@ -1,120 +1,29 @@
// Using the WebDAV server with Rigidbot 3D printer.
// Printer controller is a variation of Rambo running Marlin firmware
#include "ESP8266WiFi.h"
#include "ESPWebDAV.h"
#include "serial.h"
#include "parser.h"
#include "Config.h"
#include "config.h"
#include "network.h"
#include "gcode.h"
// LED is connected to GPIO2 on this board
#define INIT_LED {pinMode(2, OUTPUT);}
#define LED_ON {digitalWrite(2, LOW);}
#define LED_OFF {digitalWrite(2, HIGH);}
#define HOSTNAME "FYSETC"
#define SERVER_PORT 80
#define SPI_BLOCKOUT_PERIOD 20000UL
#define WIFI_CONNECT_TIMEOUT 30000UL
#define SD_CS 4
#define MISO 12
#define MOSI 13
#define SCLK 14
#define CS_SENSE 5
ESPWebDAV dav;
String statusMessage;
bool initFailed = false;
volatile long spiBlockoutTime = 0;
bool weHaveBus = false;
bool wifiConnected = false;
bool wifiConnect(const char*ssid,const char*password) {
if(ssid == NULL || password==NULL) {
SERIAL_ECHOLN("Please set the wifi ssid and password first");
}
//
wifiConnected = false;
// Set hostname first
WiFi.hostname(HOSTNAME);
// Reduce startup surge current
WiFi.setAutoConnect(false);
WiFi.mode(WIFI_STA);
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
WiFi.begin(ssid, password);
// Wait for connection
unsigned long timeout = millis();
while(WiFi.status() != WL_CONNECTED) {
blink();
DBG_PRINT(".");
if(millis() > timeout + WIFI_CONNECT_TIMEOUT)
return false;
}
DBG_PRINTLN("");
DBG_PRINT("Connected to "); DBG_PRINTLN(ssid);
DBG_PRINT("IP address: "); DBG_PRINTLN(WiFi.localIP());
DBG_PRINT("RSSI: "); DBG_PRINTLN(WiFi.RSSI());
DBG_PRINT("Mode: "); DBG_PRINTLN(WiFi.getPhyMode());
wifiConnected = true;
config.save(ssid,password);
return true;
}
void startDAVServer() {
// Check to see if other master is using the SPI bus
while(millis() < spiBlockoutTime)
blink();
takeBusControl();
// start the SD DAV server
if(!dav.init(SD_CS, SPI_FULL_SPEED, SERVER_PORT)) {
statusMessage = "Failed to initialize SD Card";
DBG_PRINT("ERROR: "); DBG_PRINTLN(statusMessage);
// indicate error on LED
errorBlink();
initFailed = true;
}
else
blink();
relenquishBusControl();
DBG_PRINTLN("WebDAV server started");
}
// ------------------------
void setup() {
// ----- GPIO -------
// Detect when other master uses SPI bus
pinMode(CS_SENSE, INPUT);
attachInterrupt(CS_SENSE, []() {
if(!weHaveBus)
spiBlockoutTime = millis() + SPI_BLOCKOUT_PERIOD;
}, FALLING);
DBG_INIT(115200);
DBG_PRINTLN("");
SERIAL_INIT(115200);
INIT_LED;
blink();
// wait for other master to assert SPI bus first
delay(SPI_BLOCKOUT_PERIOD);
network.setup();
// ----- WIFI -------
if(config.load() == 1) { // Connected before
if(wifiConnect(config.ssid(), config.password())) {
startDAVServer();
}
else {
if(!network.start()) {
SERIAL_ECHOLN("");
SERIAL_ECHOLN("Connect fail, please set the wifi config and connect again");
}
}
@@ -123,260 +32,18 @@ void setup() {
}
}
#define MAX_CMD_SIZE 96
#define BUFSIZE 4
/**
* GCode Command Queue
* A simple ring buffer of BUFSIZE command strings.
*
* Commands are copied into this buffer by the command injectors
* (immediate, serial, sd card) and they are processed sequentially by
* the main loop. The process_next_command function parses the next
* command and hands off execution to individual handler functions.
*/
uint8_t commands_in_queue = 0, // Count of commands in the queue
cmd_queue_index_r = 0, // Ring buffer read (out) position
cmd_queue_index_w = 0; // Ring buffer write (in) position
uint32_t command_sd_pos[BUFSIZE];
volatile uint32_t current_command_sd_pos;
char command_queue[BUFSIZE][MAX_CMD_SIZE];
static bool send_ok[BUFSIZE];
// Number of characters read in the current line of serial input
static int serial_count; // = 0;
/**
* Once a new command is in the ring buffer, call this to commit it
*/
inline void _commit_command(bool say_ok) {
send_ok[cmd_queue_index_w] = say_ok;
if (++cmd_queue_index_w >= BUFSIZE) cmd_queue_index_w = 0;
commands_in_queue++;
}
/**
* Copy a command from RAM into the main command buffer.
* Return true if the command was successfully added.
* Return false for a full buffer, or if the 'command' is a comment.
*/
inline bool _enqueuecommand(const char* cmd, bool say_ok=false) {
if (*cmd == ';' || commands_in_queue >= BUFSIZE) return false;
strcpy(command_queue[cmd_queue_index_w], cmd);
_commit_command(say_ok);
return true;
}
/**
* Get all commands waiting on the serial port and queue them.
* Exit when the buffer is full or when no more characters are
* left on the serial port.
*/
void get_serial_commands() {
static char serial_line_buffer[MAX_CMD_SIZE];
static bool serial_comment_mode = false;
/**
* Loop while serial characters are incoming and the queue is not full
*/
int c;
while (commands_in_queue < BUFSIZE && (c = Serial.read()) >= 0) {
char serial_char = c;
/**
* If the character ends the line
*/
if (serial_char == '\n' || serial_char == '\r') {
serial_comment_mode = false; // end of line == end of comment
// Skip empty lines and comments
if (!serial_count) { continue; }
serial_line_buffer[serial_count] = 0; // Terminate string
serial_count = 0; // Reset buffer
char* command = serial_line_buffer;
while (*command == ' ') command++; // Skip leading spaces
// Add the command to the queue
_enqueuecommand(serial_line_buffer, true);
}
else if (serial_count >= MAX_CMD_SIZE - 1) {
// Keep fetching, but ignore normal characters beyond the max length
// The command will be injected when EOL is reached
}
else if (serial_char == '\\') { // Handle escapes
if ((c = MYSERIAL0.read()) >= 0 && !serial_comment_mode) // if we have one more character, copy it over
serial_line_buffer[serial_count++] = (char)c;
// otherwise do nothing
}
else { // it's not a newline, carriage return or escape char
if (serial_char == ';') serial_comment_mode = true;
if (!serial_comment_mode) serial_line_buffer[serial_count++] = serial_char;
}
} // queue has space, serial has data
}
/**
* M50: Set the Wifi ssid
*/
inline void gcode_M50() {
for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0';
config.ssid(parser.string_arg);
SERIAL_ECHO(config.ssid());
}
/**
* M50: Set the Wifi password
*/
inline void gcode_M51() {
for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0';
config.password(parser.string_arg);
SERIAL_ECHO(config.password());
}
/**
* M52: Connect the wifi
*/
inline void gcode_M52() {
if(wifiConnect(config.ssid(), config.password())) {
startDAVServer();
}
else {
SERIAL_ECHOLN("Connect fail, please set the wifi config and connect again");
}
}
/**
* M53: Check wifi status
*/
inline void gcode_M53() {
if(WiFi.status() != WL_CONNECTED) {
SERIAL_ECHOLN("Wifi not connected");
SERIAL_ECHOLN("Please set the wifi ssid with M50 and password with M51 , and start connection with M52");
}
else {
DBG_PRINTLN("");
DBG_PRINT("Connected to "); SERIAL_ECHOLN(WiFi.SSID());
DBG_PRINT("IP address: "); DBG_PRINTLN(WiFi.localIP());
DBG_PRINT("RSSI: "); DBG_PRINTLN(WiFi.RSSI());
DBG_PRINT("Mode: "); DBG_PRINTLN(WiFi.getPhyMode());
}
}
/**
* Process the parsed command and dispatch it to its handler
*/
void process_parsed_command() {
//SERIAL_ECHOLNPAIR("command_letter:", parser.command_letter);
//SERIAL_ECHOLNPAIR("codenum:", parser.codenum);
// Handle a known G, M, or T
switch (parser.command_letter) {
case 'G': switch (parser.codenum) {
default: parser.unknown_command_error();
}
break;
case 'M': switch (parser.codenum) {
case 50: gcode_M50(); break;
case 51: gcode_M51(); break;
case 52: gcode_M52(); break;
case 53: gcode_M53(); break;
default: parser.unknown_command_error();
}
break;
default: parser.unknown_command_error();
}
SERIAL_ECHOLN("ok");
}
void process_next_command() {
char * const current_command = command_queue[cmd_queue_index_r];
current_command_sd_pos = command_sd_pos[cmd_queue_index_r];
// Parse the next command in the queue
parser.parse(current_command);
process_parsed_command();
}
// ------------------------
void loop() {
// ------------------------
if(millis() < spiBlockoutTime)
blink();
// handle the request
network.handle();
// do it only if there is a need to read FS
if(wifiConnected) {
if(dav.isClientWaiting()) {
if(initFailed)
return dav.rejectClient(statusMessage);
// has other master been using the bus in last few seconds
if(millis() < spiBlockoutTime)
return dav.rejectClient("Marlin is reading from SD card");
// a client is waiting and FS is ready and other SPI master is not using the bus
takeBusControl();
dav.handleClient();
relenquishBusControl();
}
}
// Handle gcode
gcode.Handle();
// Get the serial input
if (commands_in_queue < BUFSIZE) get_serial_commands();
if (commands_in_queue) {
process_next_command();
// The queue may be reset by a command handler or by code invoked by idle() within a handler
if (commands_in_queue) {
--commands_in_queue;
if (++cmd_queue_index_r >= BUFSIZE) cmd_queue_index_r = 0;
}
}
//
// blink
statusBlink();
}
// ------------------------
void takeBusControl() {
// ------------------------
weHaveBus = true;
LED_ON;
pinMode(MISO, SPECIAL);
pinMode(MOSI, SPECIAL);
pinMode(SCLK, SPECIAL);
pinMode(SD_CS, OUTPUT);
}
// ------------------------
void relenquishBusControl() {
// ------------------------
pinMode(MISO, INPUT);
pinMode(MOSI, INPUT);
pinMode(SCLK, INPUT);
pinMode(SD_CS, INPUT);
LED_OFF;
weHaveBus = false;
}
// ------------------------
void blink() {
// ------------------------
@@ -386,8 +53,6 @@ void blink() {
delay(400);
}
// ------------------------
void errorBlink() {
// ------------------------
@@ -402,7 +67,7 @@ void errorBlink() {
void statusBlink() {
static unsigned long time = 0;
if(millis() > time + 1000 ) {
if(wifiConnected) {
if(network.isConnected()) {
LED_ON;
delay(50);
LED_OFF;
@@ -412,4 +77,8 @@ void statusBlink() {
}
time = millis();
}
// SPI bus not ready
//if(millis() < spiBlockoutTime)
// blink();
}

View File

@@ -48,4 +48,18 @@ void Config::save(const char*ssid,const char*password) {
EEPROM.commit();
}
void Config::save() {
if(data.ssid == NULL || data.psw == NULL)
return;
EEPROM.begin(EEPROM_SIZE);
data.flag = 1;
uint8_t *p = (uint8_t*)(&data);
for (int i = 0; i < sizeof(data); i++)
{
EEPROM.write(i, *(p + i));
}
EEPROM.commit();
}
Config config;

View File

@@ -24,6 +24,7 @@ public:
char* password();
void password(char* password);
void save(const char*ssid,const char*password);
void save();
protected:
CONFIG_TYPE data;

181
gcode.cpp Normal file
View File

@@ -0,0 +1,181 @@
#include "gcode.h"
#include "config.h"
#include "parser.h"
#include "network.h"
#include "serial.h"
#include <ESP8266WiFi.h>
Gcode gcode;
void Gcode::Handle() {
// Get the serial input
if (commands_in_queue < BUFSIZE) get_serial_commands();
if (commands_in_queue) {
process_next_command();
// The queue may be reset by a command handler or by code invoked by idle() within a handler
if (commands_in_queue) {
--commands_in_queue;
if (++cmd_queue_index_r >= BUFSIZE) cmd_queue_index_r = 0;
}
}
}
/**
* Once a new command is in the ring buffer, call this to commit it
*/
void Gcode::_commit_command(bool say_ok) {
send_ok[cmd_queue_index_w] = say_ok;
if (++cmd_queue_index_w >= BUFSIZE) cmd_queue_index_w = 0;
commands_in_queue++;
}
/**
* Copy a command from RAM into the main command buffer.
* Return true if the command was successfully added.
* Return false for a full buffer, or if the 'command' is a comment.
*/
bool Gcode::_enqueuecommand(const char* cmd, bool say_ok) {
if (*cmd == ';' || commands_in_queue >= BUFSIZE) return false;
strcpy(command_queue[cmd_queue_index_w], cmd);
_commit_command(say_ok);
return true;
}
/**
* Get all commands waiting on the serial port and queue them.
* Exit when the buffer is full or when no more characters are
* left on the serial port.
*/
void Gcode::get_serial_commands() {
static char serial_line_buffer[MAX_CMD_SIZE];
static bool serial_comment_mode = false;
/**
* Loop while serial characters are incoming and the queue is not full
*/
int c;
while (commands_in_queue < BUFSIZE && (c = Serial.read()) >= 0) {
char serial_char = c;
/**
* If the character ends the line
*/
if (serial_char == '\n' || serial_char == '\r') {
serial_comment_mode = false; // end of line == end of comment
// Skip empty lines and comments
if (!serial_count) { continue; }
serial_line_buffer[serial_count] = 0; // Terminate string
serial_count = 0; // Reset buffer
char* command = serial_line_buffer;
while (*command == ' ') command++; // Skip leading spaces
// Add the command to the queue
_enqueuecommand(serial_line_buffer, true);
}
else if (serial_count >= MAX_CMD_SIZE - 1) {
// Keep fetching, but ignore normal characters beyond the max length
// The command will be injected when EOL is reached
}
else if (serial_char == '\\') { // Handle escapes
if ((c = MYSERIAL0.read()) >= 0 && !serial_comment_mode) // if we have one more character, copy it over
serial_line_buffer[serial_count++] = (char)c;
// otherwise do nothing
}
else { // it's not a newline, carriage return or escape char
if (serial_char == ';') serial_comment_mode = true;
if (!serial_comment_mode) serial_line_buffer[serial_count++] = serial_char;
}
} // queue has space, serial has data
}
/**
* M50: Set the Wifi ssid
*/
void Gcode::gcode_M50() {
for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0';
config.ssid(parser.string_arg);
SERIAL_ECHO(config.ssid());
}
/**
* M50: Set the Wifi password
*/
void Gcode::gcode_M51() {
for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0';
config.password(parser.string_arg);
SERIAL_ECHO(config.password());
}
/**
* M52: Connect the wifi
*/
void Gcode::gcode_M52() {
if(!network.start()) {
SERIAL_ECHOLN("");
SERIAL_ECHOLN("Connect fail, please set the wifi config and connect again");
}
}
/**
* M53: Check wifi status
*/
void Gcode::gcode_M53() {
if(WiFi.status() != WL_CONNECTED) {
SERIAL_ECHOLN("Wifi not connected");
SERIAL_ECHOLN("Please set the wifi ssid with M50 and password with M51 , and start connection with M52");
}
else {
SERIAL_ECHOLN("");
SERIAL_ECHO("Connected to "); SERIAL_ECHOLN(WiFi.SSID());
SERIAL_ECHO("IP address: "); SERIAL_ECHOLN(WiFi.localIP());
SERIAL_ECHO("RSSI: "); SERIAL_ECHOLN(WiFi.RSSI());
SERIAL_ECHO("Mode: "); SERIAL_ECHOLN(WiFi.getPhyMode());
SERIAL_ECHO("Asscess to SD at the Run prompt : \\\\"); SERIAL_ECHO(WiFi.localIP());SERIAL_ECHOLN("\\DavWWWRoot");
}
}
/**
* Process the parsed command and dispatch it to its handler
*/
void Gcode::process_parsed_command() {
//SERIAL_ECHOLNPAIR("command_letter:", parser.command_letter);
//SERIAL_ECHOLNPAIR("codenum:", parser.codenum);
// Handle a known G, M, or T
switch (parser.command_letter) {
case 'G': switch (parser.codenum) {
default: parser.unknown_command_error();
}
break;
case 'M': switch (parser.codenum) {
case 50: gcode_M50(); break;
case 51: gcode_M51(); break;
case 52: gcode_M52(); break;
case 53: gcode_M53(); break;
default: parser.unknown_command_error();
}
break;
default: parser.unknown_command_error();
}
SERIAL_ECHOLN("ok");
}
void Gcode::process_next_command() {
char * const current_command = command_queue[cmd_queue_index_r];
current_command_sd_pos = command_sd_pos[cmd_queue_index_r];
// Parse the next command in the queue
parser.parse(current_command);
process_parsed_command();
}

53
gcode.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef _GCODE_H_
#define _GCODE_H_
#define MAX_CMD_SIZE 96
#define BUFSIZE 4
/**
* GCode
*
* - Handle gcode
*/
class Gcode {
public:
void Handle();
private:
void _commit_command(bool say_ok);
bool _enqueuecommand(const char* cmd, bool say_ok=false);
void get_serial_commands();
void gcode_M50();
void gcode_M51();
void gcode_M52();
void gcode_M53();
void process_parsed_command();
void process_next_command();
/**
* GCode Command Queue
* A simple ring buffer of BUFSIZE command strings.
*
* Commands are copied into this buffer by the command injectors
* (immediate, serial, sd card) and they are processed sequentially by
* the main loop. The process_next_command function parses the next
* command and hands off execution to individual handler functions.
*/
unsigned char commands_in_queue = 0, // Count of commands in the queue
cmd_queue_index_r = 0, // Ring buffer read (out) position
cmd_queue_index_w = 0; // Ring buffer write (in) position
unsigned long command_sd_pos[BUFSIZE];
volatile unsigned long current_command_sd_pos;
char command_queue[BUFSIZE][MAX_CMD_SIZE];
bool send_ok[BUFSIZE];
// Number of characters read in the current line of serial input
int serial_count; // = 0;
};
extern Gcode gcode;
#endif

141
network.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include "network.h"
#include "serial.h"
#include "config.h"
#include "ESP8266WiFi.h"
#include "ESPWebDAV.h"
volatile long Network::_spiBlockoutTime = 0;
bool Network::_weHaveBus = false;
void Network::setup() {
// ----- GPIO -------
// Detect when other master uses SPI bus
pinMode(CS_SENSE, INPUT);
attachInterrupt(CS_SENSE, []() {
if(!_weHaveBus)
_spiBlockoutTime = millis() + SPI_BLOCKOUT_PERIOD;
}, FALLING);
// wait for other master to assert SPI bus first
delay(SPI_BLOCKOUT_PERIOD);
}
bool Network::start() {
wifiConnected = false;
// Set hostname first
WiFi.hostname(HOSTNAME);
// Reduce startup surge current
WiFi.setAutoConnect(false);
WiFi.mode(WIFI_STA);
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
WiFi.begin(config.ssid(), config.password());
// Wait for connection
unsigned int timeout = 0;
while(WiFi.status() != WL_CONNECTED) {
//blink();
SERIAL_ECHO(".");
timeout++;
if(timeout++ > WIFI_CONNECT_TIMEOUT/100)
return false;
else
delay(100);
}
SERIAL_ECHOLN("");
SERIAL_ECHO("Connected to "); SERIAL_ECHOLN(config.ssid());
SERIAL_ECHO("IP address: "); SERIAL_ECHOLN(WiFi.localIP());
SERIAL_ECHO("RSSI: "); SERIAL_ECHOLN(WiFi.RSSI());
SERIAL_ECHO("Mode: "); SERIAL_ECHOLN(WiFi.getPhyMode());
SERIAL_ECHO("Asscess to SD at the Run prompt : \\\\"); SERIAL_ECHO(WiFi.localIP());SERIAL_ECHOLN("\\DavWWWRoot");
wifiConnected = true;
config.save();
startDAVServer();
return true;
}
void Network::startDAVServer() {
// Check to see if other master is using the SPI bus
while(millis() < _spiBlockoutTime) {
//blink();
}
takeBusControl();
// start the SD DAV server
if(!dav.init(SD_CS, SPI_FULL_SPEED, SERVER_PORT)) {
DBG_PRINT("ERROR: "); DBG_PRINTLN("Failed to initialize SD Card");
// indicate error on LED
//errorBlink();
initFailed = true;
}
else {
//blink();
}
relenquishBusControl();
DBG_PRINTLN("WebDAV server started");
}
bool Network::isConnected() {
return wifiConnected;
}
// a client is waiting and FS is ready and other SPI master is not using the bus
bool Network::ready() {
if(!isConnected()) return false;
// do it only if there is a need to read FS
if(!dav.isClientWaiting()) return false;
if(initFailed) {
dav.rejectClient("Failed to initialize SD Card");
return false;
}
// has other master been using the bus in last few seconds
if(millis() < _spiBlockoutTime) {
dav.rejectClient("Marlin is reading from SD card");
return false;
}
return true;
}
void Network::handle() {
if(network.ready()) {
takeBusControl();
dav.handleClient();
relenquishBusControl();
}
}
// ------------------------
void Network::takeBusControl() {
// ------------------------
_weHaveBus = true;
//LED_ON;
pinMode(MISO_PIN, SPECIAL);
pinMode(MOSI_PIN, SPECIAL);
pinMode(SCLK_PIN, SPECIAL);
pinMode(SD_CS, OUTPUT);
}
// ------------------------
void Network::relenquishBusControl() {
// ------------------------
pinMode(MISO_PIN, INPUT);
pinMode(MOSI_PIN, INPUT);
pinMode(SCLK_PIN, INPUT);
pinMode(SD_CS, INPUT);
//LED_OFF;
_weHaveBus = false;
}
Network network;

37
network.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef _NETWORK_H_
#define _NETWORK_H_
#define HOSTNAME "FYSETC"
#define SERVER_PORT 80
#define SD_CS 4
#define MISO_PIN 12
#define MOSI_PIN 13
#define SCLK_PIN 14
#define CS_SENSE 5
#define SPI_BLOCKOUT_PERIOD 20000UL
#define WIFI_CONNECT_TIMEOUT 30000UL
class Network {
public:
Network() { initFailed = false;}
static void setup();
static void takeBusControl();
static void relenquishBusControl();
bool start();
void startDAVServer();
bool isConnected();
void handle();
bool ready();
private:
bool wifiConnected;
bool initFailed;
static volatile long _spiBlockoutTime;
static bool _weHaveBus;
};
extern Network network;
#endif

View File

@@ -10,6 +10,7 @@
//#include "enum.h"
#include <stdint.h>
#include <stdlib.h>
#include "macros.h"
#define strtof strtod

View File

@@ -28,6 +28,8 @@
#define MYSERIAL0 Serial
#define SERIAL_INIT(...) { Serial.begin(__VA_ARGS__); }
extern const char echomagic[] PROGMEM;
extern const char errormagic[] PROGMEM;