C64 MyHome
E' incredibile pensare di utilizzare uno storico Commodore 64 per gestire la domotica moderna... eppure si, è possibile!
Premessa:
Il mio primo computer è stato proprio il Commdore 64 che mi fu regalato da mio padre per la Cresima, da quella volta il mio mondo è cambiato ed ho imparato tantissimo da questo computer, infatti è grazie a lui se ho iniziato a programmare e ad arrivare a fare quello che faccio oggi. Probabilmente questo non sarà un software dai grandi utilizzi, anche perché si può fare più semplicemente (e comodamente) con altro hardware, ma è un modo per ringraziare e tenere vivo questo storico computer che ha cambiato la vita di moltissime persone negli anni '80.
Cosa occorre:
Per cominciare occorre far dialogare il nostro mitico C64 con il webserver e fargli inviare le frame per gestire l'impianto. Vi ricordo che in questo sito si parla sempre di domotica MyHome Bticino.
Dobbiamo quindi avere un modem WIFI per il Commodore basato su ESP8266, qui trovate un link dove possibile acquistarlo: (solo il NodeMcu Cp2102)
https://it.aliexpress.com/item/4000550036826.html?spm=a2g0o.productlist.0.0.5d5b20cbBE8avV&algo_pvid=fe7d2380-5df9-42f6-8f2d-300963713013&algo_expid=fe7d2380-5df9-42f6-8f2d-300963713013-8&btsid=0ab50f6215866859973474085ef84d&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Se volete divertirvi a crearlo da zero potete prendere il connettore della user port e stamparvi il pcb da soli, vi metto i link di questo oggetti: (i connettori si trovano anche sfusi su Ebay, come anche il modem completo)
https://it.aliexpress.com/item/32907889521.html?spm=a2g0s.9042311.0.0.27424c4dVEN1MR
Occorre ovviamente un gateway Bticino (F453,F454..), un Commodore 64 o 128 oppure uno di nuova generazione come il C64 Ultimate (con cavo user port) o il C64 reloaded.
Attenzione se utilizzate la "the final cartridge 3" o altre acceleratrici il modem potrebbe non comunicare correttamente.
Iniziamo:
Allo stato attuale il programma è in una fase sperimentale e quindi non ottimizzato, però è funzionante e va bene per inviare comandi e ricevere conferma dell'avvenuta esecuzione.
Se avete costruito il modem o se lo avete preso già fatto sarà comunque necessario caricarci il firmware per gestire la comunicazione con il web server MyHome (io uso un F453AV come gateway).
Il programma per l'ESP8266 va personalizzato perché in questo stadio non è ancora implementato un metodo per impostare i parametri wifi e indirizzo ip tramite il C64.
Possiamo utilizzare l'ide di Arduino per compilare caricare il firmware all'interno dell'ESP (tenete presente che poi non sarà più utilizzabile come modem wifi con la relativa applicazione, occorrerà riprogrammare l'ESP con il firmware del modem x C64).
(se avete già l'ide con le librerie della scheda installata saltate questo passaggio)
- Scaricatevi l'ide se non lo avete: https://www.arduino.cc/en/main/software
- Se non avete le librerie e scheda ESP installata sull'ide dovete procedere seguendo questa guida: https://www.instructables.com/id/Programming-ESP8266-ESP-12E-NodeMCU-Using-Arduino-/
Anche senza guida comunque è semplice, vi tradulo le parti fondamentali: - Aprite l'ide di Arduino, andate su file - impostazioni e nella riga "URL aggiuntive per il Gestore schede" aggiungete questa url:
https://arduino.esp8266.com/stable/package_esp8266com_index.json
- Andate su strumenti - gestione schede, cercate ESP8266, selezionate l'ultima versione ed installare la libreria, infine riavviate l'ide e siete pronti.
Copiate e incollate il seguente codice su l'ide: (personalizzate le righe che riguardano la connessione wifi e indirizzo ip, è consigliabile avere un indirizzo ip fisso così da impostare l'F453 per far connettere quetso ip senza richierede password open)
(In alternativa al copia-incolla, potete scaricare il file in fondo alla pagina C64-wifi e modificate sempre i parametri richiesti)
#include <ESP8266WiFi.h>
#include <EEPROM.h>
#define SWITCH_PIN 0 // GPIO0 (programmind mode pin)
#define LED_PIN 16 // Internal LED
#define DCD_PIN 2 // DCD Carrier Status
#define RTS_PIN 4 // RTS Request to Send, connect to host's CTS pin
#define CTS_PIN 5 // CTS Clear to Send, connect to host's RTS pin
// Global variables
String cmd = ""; // Gather a new AT command to this string from serial
bool cmdMode = true; // Are we in AT command mode or connected mode
bool callConnected = false;// Are we currently in a call
bool telnet = false; // Is telnet control code handling enabled
bool verboseResults = false;
#define MAX_CMD_LENGTH 256 // Maximum length for AT command
char plusCount = 0; // Go to AT mode at "+++" sequence, that has to be counted
unsigned long plusTime = 0;// When did we last receive a "+++" sequence
#define LED_TIME 15 // How many ms to keep LED on at activity
unsigned long ledTime = 0;
#define TX_BUF_SIZE 256 // Buffer where to read from serial before writing to TCP
// (that direction is very blocking by the ESP TCP stack,
// so we can't do one byte a time.)
uint8_t txBuf[TX_BUF_SIZE];
String speedDials[10];
const int bauds[] = { 1200 };
byte serialspeed;
String busyMsg;
byte ringCount = 0;
String resultCodes[] = { "OK", "CONNECT", "RING", "NO CARRIER", "ERROR", "", "NO DIALTONE", "BUSY", "NO ANSWER" };
enum resultCodes_t { R_OK, R_CONNECT, R_RING, R_NOCARRIER, R_ERROR, R_NONE, R_NODIALTONE, R_BUSY, R_NOANSWER };
unsigned long connectTime = 0;
enum flowControl_t { F_NONE, F_HARDWARE, F_SOFTWARE };
byte flowControl = F_NONE; // Use flow control
bool txPaused = false; // Has flow control asked us to pause?
enum pinPolarity_t { P_INVERTED, P_NORMAL }; // Is LOW (0) or HIGH (1) active?
byte pinPolarity = P_INVERTED;
//BTICINO MOD
const char* host = "192.168.1.254"; // WEBSERVER MYHOME IP
const int port = 20000; // SERVER PORT
String ssid = "vostro_ssid";
String password = "vostra_password_wifi";
bool conn = false;
String rcv ="";
int ownstate = 0;
bool busy =false;
String cmdser="";
//
// Telnet codes
#define DO 0xfd
#define WONT 0xfc
#define WILL 0xfb
#define DONT 0xfe
WiFiClient tcpClient;
//MDNSResponder mdns;
String connectTimeString() {
unsigned long now = millis();
int secs = (now - connectTime) / 1000;
int mins = secs / 60;
int hours = mins / 60;
String out = "";
if (hours < 10) out.concat("0");
out.concat(String(hours));
out.concat(":");
if (mins % 60 < 10) out.concat("0");
out.concat(String(mins % 60));
out.concat(":");
if (secs % 60 < 10) out.concat("0");
out.concat(String(secs % 60));
return out;
}
void sendResult(int resultCode) {
}
// Hold for 5 seconds to switch to 300 baud
// Slow flash: keep holding
// Fast flash: let go
int checkButton() {
long time = millis();
while (digitalRead(SWITCH_PIN) == LOW && millis() - time < 5000) {
delay(250);
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
yield();
}
if (millis() - time > 5000) {
Serial.flush();
Serial.end();
serialspeed = 0;
delay(100);
Serial.begin(bauds[serialspeed]);
sendResult(R_OK);
while (digitalRead(SWITCH_PIN) == LOW) {
delay(50);
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
yield();
}
return 1;
} else {
return 0;
}
}
void connectWiFi() {
//WIFI SSID,PASSWORD
WiFi.begin(ssid, password);
//Serial.print("\nCONNECTING TO SSID "); Serial.print(ssid);
// fixed ip for myhome
IPAddress ip(192,168,1,10);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
WiFi.config(ip, gateway, subnet);
uint8_t i = 0;
while (WiFi.status() != WL_CONNECTED && i++ < 20) {
digitalWrite(LED_PIN, LOW);
delay(250);
digitalWrite(LED_PIN, HIGH);
delay(250);
//Serial.print(".");
}
Serial.println();
if (i == 21) {
Serial.print("COULD NOT CONNET TO "); Serial.println(ssid);
WiFi.disconnect();
updateLed();
} else {
//Serial.print("CONNECTED TO "); Serial.println(WiFi.SSID());
//Serial.print("IP ADDRESS: "); Serial.println(WiFi.localIP());
updateLed();
}
}
void updateLed() {
if (WiFi.status() == WL_CONNECTED) {
digitalWrite(LED_PIN, LOW); // on
} else {
digitalWrite(LED_PIN, HIGH); //off
}
}
void disconnectWiFi() {
WiFi.disconnect();
updateLed();
}
void setCarrier(byte carrier) {
if (pinPolarity == P_NORMAL) carrier = !carrier;
digitalWrite(DCD_PIN, carrier);
}
/**
Arduino main init function
*/
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // off
pinMode(SWITCH_PIN, INPUT);
digitalWrite(SWITCH_PIN, HIGH);
pinMode(DCD_PIN, OUTPUT);
pinMode(RTS_PIN, OUTPUT);
digitalWrite(RTS_PIN, HIGH); // ready to receive data
pinMode(CTS_PIN, INPUT);
setCarrier(false);
delay(10);
Serial.begin(1200);
WiFi.mode(WIFI_STA);
connectWiFi();
//digitalWrite(LED_PIN, LOW); // on
}
String ipToString(IPAddress ip) {
char s[16];
sprintf(s, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
return s;
}
void hangUp() {
tcpClient.stop();
callConnected = false;
setCarrier(callConnected);
sendResult(R_NOCARRIER);
connectTime = 0;
}
void handleRoot() {
String page = "WIFI STATUS: ";
if (WiFi.status() == WL_CONNECTED) {
page.concat("CONNECTED");
}
if (WiFi.status() == WL_IDLE_STATUS) {
page.concat("OFFLINE");
}
if (WiFi.status() == WL_CONNECT_FAILED) {
page.concat("CONNECT FAILED");
}
if (WiFi.status() == WL_NO_SSID_AVAIL) {
page.concat("SSID UNAVAILABLE");
}
if (WiFi.status() == WL_CONNECTION_LOST) {
page.concat("CONNECTION LOST");
}
if (WiFi.status() == WL_DISCONNECTED) {
page.concat("DISCONNECTED");
}
if (WiFi.status() == WL_SCAN_COMPLETED) {
page.concat("SCAN COMPLETED");
}
yield();
page.concat("\nSSID.......: " + WiFi.SSID());
byte mac[6];
WiFi.macAddress(mac);
page.concat("\nMAC ADDRESS: ");
page.concat(String(mac[0], HEX));
page.concat(":");
page.concat(String(mac[1], HEX));
page.concat(":");
page.concat(String(mac[2], HEX));
page.concat(":");
page.concat(String(mac[3], HEX));
page.concat(":");
page.concat(String(mac[4], HEX));
page.concat(":");
page.concat(String(mac[5], HEX));
yield();
page.concat("\nIP ADDRESS.: "); page.concat(ipToString(WiFi.localIP()));
page.concat("\nGATEWAY....: "); page.concat(ipToString(WiFi.gatewayIP()));
yield();
page.concat("\nSUBNET MASK: "); page.concat(ipToString(WiFi.subnetMask()));
yield();
page.concat("\nCALL STATUS: ");
if (callConnected) {
page.concat("CONNECTED TO ");
page.concat(ipToString(tcpClient.remoteIP()));
page.concat("\nCALL LENGTH: "); page.concat(connectTimeString()); yield();
} else {
page.concat("NOT CONNECTED");
}
page.concat("\n");
//webServer.send(200, "text/plain", page);
delay(100);
}
/**
Turn on the LED and store the time, so the LED will be shortly after turned off
*/
void led_on()
{
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
ledTime = millis();
}
/**
Perform a command given in command mode
*/
void command()
{
cmd.trim();
if (cmd == "") return;
//Serial.println();
String upCmd = cmd;
upCmd.toUpperCase();
//comando own
if (upCmd.substring(1,2)="*") {
//Serial.println(upCmd);
tcpClient.print(upCmd);
tcpClient.flush();
cmd="";
return;
}
if (upCmd == "AT") sendResult(R_OK);/**** Just AT ****/
/**** Disconnect WiFi ****/
else if (upCmd == "ATC0") {
disconnectWiFi();
sendResult(R_OK);
}
/**** Connect WiFi ****/
else if (upCmd == "ATC1") {
connectWiFi();
sendResult(R_OK);
}
/**** See my IP address ****/
else if (upCmd == "ATIP?")
{
Serial.println(WiFi.localIP());
sendResult(R_OK);
}
/**** Unknown command ****/
else sendResult(R_ERROR);
cmd = "";
}
void OwnRecive(String message) {
//Serial.print(rcv);
busy=false;
if (rcv=="*#*1##") {//ACK
switch (ownstate){
case 0: //richiesta autenticazione
if (tcpClient.connected()) {
//Serial.println("richiesta sessione");
tcpClient.print("*99*0##");
tcpClient.flush();
busy=true;
}
ownstate=1;
break;
case 1: //esito auth
ownstate=2; //ok
break;
case 2://esiti comandi da seriale
Serial.print(rcv);
break;
}
} else if (rcv=="*#*0##") {//NACK
Serial.print(rcv);
if (ownstate==1) {ownstate=0;}//rifiuto connessione
} else {//dati
if (rcv.length()>=12 && ownstate==1) { //password open
String pass1 = rcv.substring(2,11);
Serial.println("pass required");
}
}
rcv="";
}
/*************************************************************************************************************************************************************************************************************
Arduino main loop function
**************************************************************************************************************************************************************************************************************/
void loop()
{
//WiFiClient client;
if (conn==false) {
if (!tcpClient.connect(host, port)) {
conn=false;
Serial.println("connection failed");
delay(3000);
return;
} else {
conn=true;
}
}
if (tcpClient.connected()) {
//Serial.println("connesso");
// wait for data to be available
unsigned long timeout = millis();
while (tcpClient.available() == 0 && busy==true) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
tcpClient.stop();
delay(5000);
return;
}
}
if (tcpClient.available()){
while (tcpClient.available()>0) {
char c = tcpClient.read();
rcv+=c;
}
OwnRecive(rcv);
}
} else { //client connesso
//Serial.println("disconnected");
ownstate=0;
conn=false;
}
/**** AT command mode ****/
// In command mode - don't exchange with TCP but gather characters to a string
if (Serial.available())
{
char chr = Serial.read();
// Convert uppercase PETSCII to lowercase ASCII (C64) in command mode only
if ((chr >= 193) && (chr <= 218)) chr-= 96;
// Return, enter, new line, carriage return.. anything goes to end the command
if ((chr == '\n') || (chr == '\r'))
{
command();
}
// Backspace or delete deletes previous character
else if ((chr == 8) || (chr == 127) || (chr == 20))
{
cmd.remove(cmd.length() - 1);
}
else
{
if (cmd.length() < MAX_CMD_LENGTH) cmd.concat(chr);
}
}
delay(10);
}
Una volta modificate le seguenti righe con i vostri paramteri sarete pronti per caricare il firmware:
const char* host = "192.168.1.254"; // INDIRIZZO IP WS (ES.F453)
const int port = 20000; // PORTA PER INVIO COMANDI (DI SOLITO 20000)
String ssid = "vostro_ssid"; // IL NOME DELLA VOSTRA RETE WIFI
String password = "vostra_password_wifi"; // E LA RELATIVA PASSWORD
IPAddress ip(192,168,1,10); //INDIRIZZO IP FISSO DELL NODEMCU
IPAddress gateway(192,168,1,1); // GATEWAY DEL VOSTRO MODEM/ROUTER
IPAddress subnet(255,255,255,0); // SUBNET DELLA VOSTRA RETE
Per caricare il firmware basta selezionare sull'ide il tipo di scheda da programmare (ESP8266), la porta COM dove connessa la scheda e cliccare su carica.
Il led integrato sulla scheda mostra lo stato della connessione wifi (lampeggiante=in connessione, Fisso=connesso, Spento=disconnesso)
Ora non rimane che caricare il programma sul C64, visto che sarebbe lungo ve lo inserisco come allegato ed in alternativa vi metto un semplice programma per inviare una frame al sistema e ricevere lo stato:
10 open5,2,0,chr$(8):poke665,73-(peek(678)*30)
20 get#5,a$:if a$<>"" then print a$;goto 20
30 print"Scomandi my home"
32 print"q1 luce1 on *1*1*11##"
34 print"2 luce1 off *1*0*11##"
36 print"q esci"
40 geta$
50 if a$="q"then close5:stop
60 if a$="1"then print#5,"*1*1*11##":gosub 200
70 if a$="2"then print#5,"*1*0*11##":gosub200
100 goto 40
200 t=0:r$="":print"sqqqqqq "
210 get#5,a$:t=t+1:if t>100 then goto250
220 r$=r$+a$:if len(r$)<6 then goto210
230 print"sqqqqqqrisposta: ";r$
240 return
250 print"sqqqqqqtimeout":return
(all'interno di print) s è il tasto ctrl-home, S è il tasto shift+ctrl-home, q è il tasto cursore giù
Il baudrate impostato sulla scheda e sul C64 è 1200.
Ora non vi resta che provare a modificare il codice basic inserendo i comandi per i vostri attuatori!!!
Download:
c64-myhome.zip - file .prg di esempio domotica luci e automazioni per C64
C64_wifi.zip - file da copiare sul' ide Arduino per programmare il NodeMcu per la connessione con il gateway Bticino (da personalizzare con i parametri della vostra rete)
firmware_wifi.zip - file da compilare su ide Arduino per riprogrammare il NodeMcu come modem C64 (io uso il modello CP2102, ma dovrebbe andare anche con il modello CH340G)
IMPORTANTE!
l'indirizzo ip del NodeMcu deve essere tra quelli consentiti alla connessione senza password sul gateway Bticino.
Requisiti del sistema :
NodeMcu EPS8266 (ho usato il Cp2102), Commodore 64 o 128, Gateway BTicino.