Your Valentine’s Note Printer

By on February 7, 2018
Pin It


[bctt tweet=”Using a telegram bot, we are going to print remote messages coming from Telegram with a small thermal printer. ” username=”OpenElectronics”]

An extremely versatile solution that can be used for many things: ranging from printing out menus and orders in a restaurant too, why not, sending out love notes to our “Valentine” and not just on Valentine’s Day.


Do you remember the Fish’n Tweets project? It allowed us to control Fishino’s I/Os using tweets. Well, that was our first experiment about the interaction between Fishino and the world of social networks. The application is still valid today, however, given the “slowness” of Twitter, sometimes the time elapsed between a command and its execution can be over 20 seconds.

Besides, Twitter is quite complex to handle, especially in the initial phase when we have to create the access keys for the “robot” user, etc.

Since we wanted to come back on the subject and since Valentine’s Day is around the corner, we thought to hit two birds with one stone and create an application, however this time it is based on the instant messaging platform Telegram, and it can be a suggestive way to send out customized messages to your special one, printed using a micro printer.

Messages can both be customized or selected from a database set on the server. The application proposed here is not limited just to the day of the lovers, but it can be useful in your everyday life, because it also implements a managing feature for a “shopping list”, which allows saving a sequence of products, and send their names out through a Telegram message anytime, and then it allows to read it and/or print it, everything in remote mode. This application can, for example, be useful in a restaurant, where the customer can place their order which will be automatically printed at the checkout register.

The application allows introducing an interface library for Telegram that has been developed specifically, allowing not only to control Fishino from anywhere in the world, but also to manage its inputs and, if we want, to send us notifications for specific events, still using a Telegram message. The library is equipped with necessary “safety” tools: the access can be left open for everyone, or limited to specific users or, if need be, it can be left open except for some “unwanted” users. As you will see, the library is quite modular and easy-to-use, hiding the technical details of the implementation and the Fishino communication from the user. These applications can also work with an Arduino board equipped with Wi-Fi shield, however that depends on the shield. The most commonly available (and cheapest) doesn’t support the (SSL/HTTPS) secure connection, therefore it cannot be used for interfacing with the social requirement, such as Twitter, Telegram and more.

However, if you have a more recent shield, you can adapt the library to it.


Telegram and its “bots”

What is a “bot”? In short, it is a “virtual” account on Telegram, which doesn’t require telephone numbers, personal data etc. This kind of account must be created by a “real” account; other than that, it can work as a simulated user, by receiving and sending out messages and more.

We are interested in something that could interact both with our Fishino and with one or more users; therefore, we are going to create a bot that will be controlled by Fishino and will be able to answer our requests and/or send us messages.

So, the bot is a robot that can be programmed in order to carry out specific actions. Bots:

  • do not show their status (online/off-line); they are always active and can (almost) instantaneously”read” our messages;
  • they have a limited memory; old messages can disappear from the server a short while after processing;
  • they cannot start out a conversation but they can answer if questioned; in order to communicate with a bot, this must be added to a group or we must send the first message to it;
  • they have a “username” that always ends in “-bot”; for instance, TriviaBot, Fishino_bot;
  • even when added to a group, it will not receive all the messages if not set up for this purpose.


Let’s create our bot

We are going to start by creating the bot we will need to interact with Fishino.

First, if you still haven’t done it, you have to download and install Telegram; in the pictures in these pages you will see the installation on a Linux desktop (we did it in order to capture screenshots), but normally the application is installed on a smartphone.

During the installation, you will be asked to confirm your phone number and/or a security code, depending on the platform. Once installed and launched, you will see the screen shown in the figure.



In order to create bots you have to some of the “father of all bots”, which is the user BotFather, all you have to do is type its name in the search box.



After that, you will see the introductory screen of BotFather; click on Start (in the bottom) to launch the bot; BotFather will respond with an explanatory message containing a list of the main available commands.



Since we want to create our bot, we’re going to click on the/newbot command.



BotFather will ask to give our boss a name; that’s became a random one, like Pippo.



We will be asked to assign a “username” to our bot; this must be unique and end in “-bot”; in case we choose an already taking username, BotFather will notify us and force us to change it. In this case, PippoBot is already taken (of course!) So we are going with PippoRobot that happens to be available; BotFather will notify our bot has been created successfully and will give us instruction on how to remotely use it. As you can see, our bot can be reached at the Telegram address:


And, in order to control it, we have to get the security token provided by BotFather:


Use this token to access the HTTP API:




The token (here hidden) is essential to guarantee that no one can tamper with our bot. This token must be inserted in the code of our sketch, as we will see later, so write it down and, above all, keep it secret since someone who knows this token can do anything with to bot, even delete it.

Now, let’s click on  and then on Start, in order to launch our bot by sending a starting message.



And voilà! We have created our first Telegram bot!


FishGram library

Once our beautiful bot has been created, to make it useful we need to be able to exchange messages with it, not only from the Telegram app but also through our Fishino. To interact, the Telegram bot uses the HTTPS protocol in a very specific format; in particular, commands can be given using the instructions HTTP GET and POST and in the HTTP headers sent, our token must always be present. For instance, a request like ‘getUpdates’, which allow us to receive messages received by the bot, has the following format:


GET /botTOKEN/getUpdates?offset=nnn&timeout=4&limit=1&allowed_updates=messages HTTP/1.1

User-Agent: FishGram 1.0.0



In this request format:

  • TOKEN is our access token, see paragraph above;
  • offset=nnnn reresents the first message ID we are interested in;
  • limit=1 tells Telegram to provide just one message per request (Telegram can provide more messages with each refresh request);
  • allowed_updates=messages indicates that we are only interested in messages and not, for instance, to audio and/or image files.

We can try the command directly from the browser, by inputting the following text in the address bar (replace TOKEN with your access token):


Of course, you have to write everything on one line. Telegram will respond with a text in JSON format like the following one:


“message”:{“message_id”:2,”from”:{“id”:nnnnnnnnn,”first_name”:”Massimo”,”last_name”:”Del Fedele”},”chat”:{“id”:nnnnnnnnn,”first_name”:”Massimo”,”last_name”:”Del Fedele”,”type”:”private”},”date”:1483380231,”text”:”ciao Pippo”}}]}


As you can see, the response text contains a lot of info, including the sender of the message, the message ID (which can be used, for instance, in a later call by sending it as offset=id+1 and ask for messages starting from the next one) etc.

It is easy to understand that, without a dedicated software library, the functioning of the whole thing can be quite complex. If sending out the request is relatively simple (all you have to do is use a FishinoClient object, connected to the required address, and send out a series of character strings), analyzing the returned JSON is not as easy, especially given the limited resources of our Fishino, especially in terms of RAM memory.

For this reason, the JSONStreamingParser, written for the Fish’n Tweets project, comes to our help; its library can “digest” Telegram’s output, character by character, without the need to memorize anything; and it can also be divided in many elements like name:value and, for each received element, call a function defined by us.

Since explaining the library’s functioning would require half the pages of this issue, maybe more, we will start from these small example sketches in order to show how it works; anyone who wants to dig deeper into it, can find the source code of the library, along with many comments and therefore relatively easy to understand. However, during the explanation of the functioning, we will talk about some peculiarities and apparently strange choices we made when writing the library itself.




A first, very easy sketch

Let’s start… From the beginning! We will try to display the messages sent to our bot on the serial Monitor; in order to do so, the sketch is really really simple. For these (first) example, we will insert the full code (including the Fishino’s initialization, the connection to the Wi-Fi router, etc., in order to allow anyone to test it “on-the-fly” by simply copying the code in the IDE; for the next examples, we will omit all of these and just insert the specific code of FishGram (List 1).


#include <Fishino.h>
#include <JSONStreamingParser.h>
#include <FishGram.h>
#include <SPI.h>
#define MY_SSID “MY_SSID”
// if you want a dynamic IP just comment / delete the following 3 lines
#define IPADDR 192, 168, 1, 251
#define GATEWAY 192, 168, 1, 1
#define NETMASK 255, 255, 255, 0
#ifdef IPADDR
IPAddress ip(IPADDR);
#ifdef GATEWAY
IPAddress gw(GATEWAY);
IPAddress gw(ip[0], ip[1], ip[2], 1);
#ifdef NETMASK
IPAddress nm(NETMASK);
IPAddress nm(255, 255, 255, 0);
// fishgram event handler -- just display message on serial port
bool FishGramHandler(uint32_t id, const char *firstName, const char *lastName, const char *message)
Serial << F(“Ho ricevuto un messaggio da ‘”) << firstName << “’\n”;
Serial << F(“Il messaggio dice: ‘”) << message << “’\n”;
return true;
void setup(void)
while (!Serial)
Serial << F(“Fishino RESET FAILED, RETRYING...\n”);
Serial << F(“Fishino WiFi RESET OK\n”);
Serial << F(“Connecting to AP...”);
while(!Fishino.begin(MY_SSID, MY_PASS))
Serial << “.”;
Serial << “OK\n”;
#ifdef IPADDR
Fishino.config(ip, gw, nm);
Serial << F(“Waiting for IP...”);
while(Fishino.status() != STATION_GOT_IP)
Serial << “.”;
Serial << F(“OK\n”);
// start FishGram
void loop(void)



As you can see, a big part of this sketch is Fishino’s initialization, router connection, etc. As for the FishGram, we basically find three points to be relevant, the first one being the function to manage events (which we will talk about shortly):

bool FishGramHandler(uint32_t id, const char *firstName, const char *lastName, const char *message)


Second is “FishGram initialization”:

// start FishGram




And third is the call in the loop:



Easy, right? But how does it work? Well, unlike most of the sketches we are used to, FishGram is event-based; after the initialization, it stays almost invisible and, above everything else, it requires very few process and memory resources. The important thing is that the FishGram.loop(); call inside the loop is executed often.

This call, which normally lasts only a few tens of microseconds, allows the library to manage an internal timer and to recall, at preset intervals, the Telegram server and require possible updates.

If there are no updates, it goes back to the idle mode; if there is something new, it will pick it up, organize it and call the event function FishGramHandler() and pass to it parameters like ID, name and last name of the sender and the message text.

With this function, you can, therefore, print what we receive on the serial port.

The two setup calls, i.e. FishGram.event() and FishGram.begin(), are used, respectively, to connect our event managing function and to communicate FishGram who our bot is using the token. Therefore, inside the sketch, we will not have to remember to ask FishGram for updates “once in a while”, in fact, thanks to this mechanism, the library itself will take care of notifying us if and only if something interesting comes from our bot.

What are the disadvantages of this method? Basically, there is only one: if FishGram() is not executed often enough, the program will not respond well. For instance, if we were to insert a delay(1000) inside the loop(), the library would only manage to process one received character per second, therefore many days would pass before having a response or, most probably, Telegram server would shut the connection down before that.

So it is definitely possible (in fact, this is the actual purpose of this system!) to make our Fishino execute something else, while it waits for messages, however, this something else must not block execution of loop() nor take too much time. Therefore, no delay(), or while() with long waiting time and/or wait for buttons to be pressed etc. Everything must be executed without blocking the loop, therefore with millis() for delays and (on-the-fly) controls for possible buttons to be detected.

There are some alternatives which however partly cancel out the advantages of this approach; for instance, if we want to wait for an I/O to go to level HIGH, instead of doing this:





We can write:





So that FishGram keeps being called during the wait. Similarly, instead of using:




We should proceed by using millis() this way:

uint32_t tim = millis() + 1000;

while(millis() < tim)



But is it possible to add a function like the one below to the library?



Of course, it is… However if the message doesn’t come, the sketch stays blocked indefinitely or for a preset time without being able to do anything else, so this is why we chose not to introduce it.

On the Serial Monitor, you will see something like this:


Fishino WiFi RESET OK

Connecting to AP…OK

Waiting for IP……..OK


I have received this message from ‘Massimo’

the message says: ‘Ciao Pippo, come stai?’


Sending a message from Fishino to a user

As you might remember, in the paragraph about the differences between a bot and a human there was a passage apparently not relevant but actually really important: a bot cannot start a conversation. Therefore we cannot tell our Fishino “find Giuseppe and send him a message”. This was a really precise choice by the developers of Telegram’API, in order to prevent an improper use of the bot. As you might very well imagine, if this was a possibility, it would be really easy to create a spam-bot capable of sending out thousands of unwanted messages to anyone! So, what can we do? Simple, once we have sent at least one message to our bot, it will have the chat ID (which is also the ID of the sender) and will, therefore, be able to respond to this user and to possible other users who contact them. Now let’s see a small modification to this sketch which allows us to get a feedback from FishGram on our mobile phone; in order to do this, all you have to do is edit the event manager function as in List. Then, in the next message sent, our bot, besides showing the output on the serial Monitor, will be able to respond as shown in the figure.



The function FishGram.sendMessage() behaves as usual since it doesn’t have to wait for anything: it is called wherever we want, it carries out its task and then it lets us continue.

The ID, the first parameter, is the user ID to which want to send a message; the second one is the actual message.


// fishgram event handler -- display message on serial port and give feedback to sender
bool FishGramHandler(uint32_t id, const char *firstName, const char *lastName, const char *message)
Serial << F(“Ho ricevuto un messaggio da ‘”) << firstName << “’\n”;
Serial << F(“Il messaggio dice: ‘”) << message << “’\n”;
String s;
s = “Ciao, “;
s += firstName;
s += “, ho ricevuto il tuo messaggio ‘”;
s += message;
s += “’”;
FishGram.sendMessage(id, s.c_str());
return true;



The library in detail

As you can see from the two previous examples, the library is quite simple; however, it contains some useful functions that we illustrated below. The first one is:


// end – and library

bool end(void);


And it is basically never used unless you want to use FishGram sporadically and free all the memory it occupies.


//deletes local data

FishGramClass &clear(void);


In practice, it is not crucial; it frees memory used by FishGram to the next event.


//Enables/disables the list of  //admitted users

FishGramClass &restrict(bool b = true);

FishGramClass &noRestrict(void) { return restrict(false); }


These last two functions are very useful in order to execute a control on who can send messages to our bot; if activated using restrict() the function automatically discards all messages coming from IDs not included in the list at the following point.


//Adds a user ID to the list//of admitted users

FishGramClass &allow(uint32_t id);


This function allows to add a user ID to the list of admitted users; if the function is activated using restrict(), the bot will only accept messages coming from the users on this list.


// Adds a user ID to the list //of blocked users

FishGramClass &block(uint32_t id);


In List 3 on the other hand, we can see the list of blocked users; any ID on this list is considered as spam and therefore ignored.


// set the number of previous messages to be retrieved
// -1 for ALL (use with caution!), 0 for none.
// must be called before the begin ()
FishGramClass & recoverOldMessages (uint32_t n = (uint32_t) -1);


At launch, FishGram’s default reads the last available message, it discards it and waits for new messages; sometimes it is desirable to also read messages preceding the launch, which can be done using this function. If we introduce recoverOldMessages(10), for instance, at launch, FishGram will retrieve the 10 previous messages:


//Sets the managing function//for users

FishGramClass &event(FishGramEvent e);


This function installs the event manager, as already mentioned:


//Sends a message to an ID 

bool sendMessage(uint32_t const &id, const char *msg);

bool sendMessage(uint32_t const &id, const __FlashStringHelper *msg);

these two functions allowed to send a message through FishGram to a specific ID:

// Sends a message to a specific ID piece by piece

bool startMessage(uint32_t const &id, uint16_t len);

bool contMessage(const char *msg);

bool contMessage(const __FlashStringHelper *msg);

bool contMessage(char c);

bool endMessage(void);



These five functions allow to send a message piece by piece, therefore they are useful if RAM memory is limited. The only downside is that we have to know the overall length of the message, which must be passed as a parameter to startMessage(). The message can then be sent multiple times using contMessage(), and terminated with endMessage().


The Note Machine

After taking a look at the FishGram library in detail, we can see another more complex application for it. In principle, things are quite simple, we just need to “turn” messages in commands for our Fishino, execute these commands and send a possible response/confirmation to the sender. As we have mentioned in the introduction, we thought about creating a system that allows to:

  • print messages coming from Telegram;
  • respond to a particular message with a quote or a romantic message;
  • preened a quote over a romantic message;
  • manage a list of objects that could correspond to a shopping list;
  • read and print said list.


To be more specific, commands are implemented by analyzing the start of the message sent and trying to locate the following keywords:


  • stampa sends the rest of the text message to the printer;
  • romantico select a romantic quote from a database and sends it back as a response;
  • citazione select a quote from a database and sends it back as a response;
  • stampa romantico select a romantic quote from a database and prints it;
  • stampa citazione select a quote from a database and prints it;
  • aggiungi adds an item to the shopping list;
  • rimuovi removes an item from the shopping list;
  • mostra lista sends the content of the shopping list as a response;
  • stampa lista prints the shopping list;
  • svuota lista clears the shopping list;
  • help shows a list of the available commands (this list).

For instance, by printing the message ‘stampa Date tonight?’ The sentence ‘Date tonight?’ Will be printed immediately and, at the same time, we would receive a confirmation message on Telegram.



Now, let’s take a look at the most important sections of the application. First of all, we need a function to manage the event coming from Telegram (the function that in the two small examples above only had to send the message to the serial and/or send it back to the sender as confirmation). In this case things are slightly more complicated since we have to analyze the text of the message in order to extract the command. Then, we need the command list, saved in some “convenient” way and a series of functions to handle those commands.

The simplest way to implement the whole thing is by using a “linked list”, which is a list of elements linked together to which you can add or remove (this last function is not used here) some elements. Those elements are composed of the command text combined with its managing function.

Let’s start with the latter, declared as in list 4.

The initial typedef is used to provide an abbreviated syntax to manage the functions that are going to execute our commands. In short, these functions have ID, name and last name of the sender as parameters and the message body without the name of the command; by returning a Boolean value-based result of the command itself (this value is not used here, but it might be useful for different commands).

struct CommandElement, on the other hand, describes the actual command, i.e. the text (name) and the pointer to its managing function (handler).

In the struct (which is basically the same thing as a class, except for some minor differences) we have also inserted a builder to initialize it.

The FLASH_HELPER is a macro (#define) which depends on the controller used; at 8 bits it is translated in__FlashStringHelper, in order to use the strings and flash memory (and therefore save on precious RAM), while at 32 bits is translated in a simple char, because for these type of controllers there is no difference between strings in the RAM or in the flash memory.


The command list has therefore represented this way:


List<CommandElement> commandList;


Here, we use a template written by us in order to handle a list of generic objects.

Why a template (and most of all… What is a template ???), Why not using a ready-made one?

For the first question and a half, we can answer by saying that a template is what is called a generic class, meaning it can be used to handle different types of objects.

So, if we wanted to create a list of potatoes, we would just have to write


List<Potato> listPotato;


And we would get what we want it right away without having to write a single line of additional code. Likewise, if we wanted a list of roses, we could write:


List<Rose> listRose;


That’s very different from having to rewrite the whole code each time to handle different types of data!

Template List can be found in the List.h file inside the project folder; any future version of the library suite might gather some code that can be reused, as in this case, in a dedicated library.

By analyzing the related code, we can notice that the template provides functions to add, delete and scroll every object of the list; it is not a particularly complex nor performing code, but it gets the job done for sure.

As for the second question… There is no right answer. It is true there are dozens of implementations of Lists, Arrays etc. that can be found online; we simply wanted to write a limited (and easily understandable) one, fit to our goal, also for instructional purposes.

Once the comment list is declared, we have to fill it, and this can be done with the following lines:


commandList.add(new CommandElement(F("aggiungi") , Cmd_AggiungiLista));

commandList.add(new CommandElement(F("rimuovi") , Cmd_RimuoviLista));

commandList.add(new CommandElement(F("mostra lista") , Cmd_MostraLista));

commandList.add(new CommandElement(F("stampa lista") , Cmd_StampaLista));

commandList.add(new CommandElement(F("svuota lista") , Cmd_SvuotaLista));

commandList.add(new CommandElement(F("stampa romantico") , Cmd_StampaRomantico));

commandList.add(new CommandElement(F("romantico") , Cmd_Romantico));

commandList.add(new CommandElement(F("stampa citazione") , Cmd_StampaCitazione));

commandList.add(new CommandElement(F("citazione") , Cmd_Citazione));

commandList.add(new CommandElement(F("stampa") , Cmd_Stampa));

commandList.add(new CommandElement(F("help") , Cmd_Help));



Quite easy, right? The add function adds a new element (CommandElement) and initializes it using the name of the command and the corresponding managing function (handler).

For instance, the first line implements the command “add “managed by the function Cmd_AggiungiLista().


Now, let’s get back to the event manager of FishGram, which uses the following code:


// fishgram event handler

bool FishGramHandler(uint32_t id, const char *firstName, const char *lastName, const char *message)


CommandElement *elem = commandList.head();



if(starts(message, elem->name))


message += strlen_P((const char *)elem->name);

while(*message && isspace(*message))


return elem->handler(id, firstName, lastName, message);


elem = elem->next();


// command not found

FishGram.sendMessage(id, F("Comando sconosciuto - inviare 'help' per lista comandi"));

return false;




All it does is scrolling the whole list of commands, by comparing them with the start of the message received; when there is a correspondence, it stops searching and executes the corresponding command.

If it doesn’t manage to find a correspondence, it sends an error message to the sender (line before the last one).

Function starts() called by the handler is a tiny helper (utility function) controlling if a text starts with a predetermined string, defined a few lines above:


bool starts(const char *msg, const FLASH_HELPER *cmd)


char c;

uint16_t i = 0;

while( (c = charAt(cmd, i++)) != 0)

if(toupper(*msg++) != toupper(c))

return false;

return true;





All we have to do now is take a look at one of the command managing functions; for the sake of simplicity, we’ll see the one implementing the “print” command:


bool Cmd_Stampa(uint32_t id, const char *firstName, const char *lastName, const char *str)


// print the message


// send a confirmation back to bot

String ans = helloMsg(firstName, lastName);

ans += F("ho stampato il tuo messaggio '");

ans += str;

ans += "'";

FishGram.sendMessage(id, ans.c_str());

return false;




This function prints out, upon request, the message (sendToPrint(str)) and returns a confirmation message, designed to save repetitions with the helloMsg (which creates a strings composed of “Hello <name>, “, adding the text “I have printed your message”, the message text and the and closure double quote, and send it back to the sender using the FishGram.sendMessage().



What about quotes? And the shopping list?

We are only going to provide a brief description of these two features; the code is not particularly complex but it would make the product too heavy nonetheless. However, will be included among the examples in Fishino’s libraries.

The quotes (and the romantic quotes) must be implemented by taking advantage of an external server, using a PHP program containing a series of preset sentences and an algorithm aim to generate a random one with each access, unless we require a specific one.

Why an external PHP? Couldn’t we just implement them on Fishino, maybe using an SD card? Yes, but not with the UNO and GUPPY versions we have chosen for the application, especially for space reasons. In fact, these versions do not have enough memory to handle both FishGram and the SD memory card. By using the (little) flash memory left free by the application, we could only insert a few sentences, which could have resulted in repetitions in a short period of time.

By using a Fishino Mega or, even better, a Fishino32, we don’t have any of this problems and it is also possible to locally implement a big quote database on a SD memory card or even directly on the flash memory on the controller.

Back to the quotes, the request takes place over the HTTP protocol (due to the limits of Fishino we cannot open more than one HTTPS connection at a time) by a simple GET request to a pass found on The PHP returns a simple text string with this format:


ID, LEN, citazione


Where ID is an ID number of the quote in order to “fish it out”, for instance, to send a print confirmation to the sender, which is necessary in order to use the functions startMessage() and correlated functions from the FishGram library (do you remember them? They allow to send a message even character by character, without the need to download the whole thing on Fishino).

The code for requesting a quote from the server can be found in modules Cit.h and Cit.cpp, also contained in the applications folder, and it is easily understandable and well commented.


As for the shopping list… We once again made use of our List template, since it is perfect to handle this kind of data; the declaration is as follows:


//The shopping list

struct ShoppingListElement : public ListElement<ShoppingListElement>


char *item;

ShoppingListElement(const char *s) { item = strdup(s); }

virtual ~ShoppingListElement() { if(item) free(item); }





As you can see, we have first defined a new struct containing the piece of data we want (a simple char pointer that will contain a text string dynamically allocated through strdup()); the new thing here is the destructor (~ShoppingListElement()) that takes care of freeing dynamic memory when we delete the element.

List determination is therefore immediate, as you can see in line following the struct, and it is used in the same way of the command list. In this case, we also use the remove() function to delete, upon request, some elements from the list.

For instance, if we want to add “Pasta” to the list using the code, we can write:


shoppingList.add(new ShoppingListElement(“Pasta”));

As already mentioned, our implementation of List is far from complete; it lacks, for instance, a search function that would be useful in this case but would have made the code a whole lot more complicated or limited its generic nature. In order to search list for an object we would have to “manually” scan it completely, just like the event manager FishGram we have seen above.


From openstore

Fishino Guppy

Thermal Printer

Thermal Printer Paper – 34′

About Boris Landoni

Boris Landoni is the technical manager of Skilled in the GSM field, embraces the Open Source philosophy and its projects are available to the community.

Leave a Reply

Your email address will not be published. Required fields are marked *